using JiDangJia.Core;
namespace JiDangJia.Views;
public sealed class ManualFinancePage : ContentPage
{
private readonly BusinessService service;
private readonly FieldSet fields;
private readonly VerticalStackLayout categories=Ui.Stack();
private readonly Button income,expense;
private readonly Dictionary<string,string> selections=new();
private string direction="EXPENSE";
public ManualFinancePage(BusinessService service)
{
this.service=service;Title="手工记账";
fields=new(new(){["occurDate"]=Display.Day(DateTime.Today),["operatorId"]=service.Owner.Id},
[new("amount","金额(元)",FieldKind.Number,true),new("occurDate","发生日期",FieldKind.Date,true),new("remark","备注",FieldKind.Notes)]);
income=Ui.Button("收入",()=>Select("INCOME"),false);expense=Ui.Button("支出",()=>Select("EXPENSE"));
var directions=new Grid{ColumnDefinitions=[new(GridLength.Star),new(GridLength.Star)],ColumnSpacing=8};directions.Add(income);directions.Add(expense,1);
var body=Ui.Stack(directions,Ui.Card(categories),Ui.Card(fields.View),
Ui.Text("资方利息可在资金成本页面结算。已自动生成的流水,请勿再次手工记账。",11,Ui.Muted),Ui.Button("确认记账",Save));
body.Padding=new Thickness(18,16,18,28);Content=new ScrollView{Content=body};
}
protected override void OnAppearing(){base.OnAppearing();RenderCategories();}
private Task Select(string value){direction=value;RenderCategories();return Task.CompletedTask;}
private void RenderCategories()
{
income.Background=direction=="INCOME"?Ui.GoldBrush:new SolidColorBrush(Ui.Cream);income.TextColor=direction=="INCOME"?Ui.Ink:Ui.Bronze;
expense.Background=direction=="EXPENSE"?Ui.GoldBrush:new SolidColorBrush(Ui.Cream);expense.TextColor=direction=="EXPENSE"?Ui.Ink:Ui.Bronze;
var choices=FinanceCategories.Read(service.Db,direction);var selected=selections.GetValueOrDefault(direction,"");
if(!choices.Any(r=>r.Text("code")==selected)){selected="";selections.Remove(direction);}
categories.Clear();categories.Add(Ui.Section("财务分类 *"));
var manage=Ui.Button("管理分类 · 修改 / 停用",()=>Navigation.PushAsync(new FinanceCategoryPage(service,direction)),false);manage.HeightRequest=42;categories.Add(manage);
var grid=new Grid{ColumnDefinitions=[new(GridLength.Star),new(GridLength.Star),new(GridLength.Star)],ColumnSpacing=6,RowSpacing=6};
for(int i=0;i<choices.Count;i++)
{
if(i%3==0)grid.RowDefinitions.Add(new(GridLength.Auto));
var row=choices[i];var code=row.Text("code");
var button=Ui.Button(row.Text("label"),()=>{selections[direction]=code;RenderCategories();return Task.CompletedTask;},selected==code);
button.HeightRequest=40;button.FontSize=12;button.Padding=new Thickness(5,0);button.CornerRadius=10;button.LineBreakMode=LineBreakMode.TailTruncation;
SemanticProperties.SetDescription(button,row.Text("label")+(selected==code?",已选择":""));grid.Add(button,i%3,i/3);
}
int added=choices.Count;if(added%3==0)grid.RowDefinitions.Add(new(GridLength.Auto));
var add=Ui.Button("+ 新增分类",()=>Navigation.PushAsync(new FinanceCategoryEditor(service,direction,created:code=>selections[direction]=code)),false);add.HeightRequest=40;add.FontSize=12;add.Padding=new Thickness(3,0);add.CornerRadius=10;add.BorderColor=Ui.Gold;add.BorderWidth=1;grid.Add(add,added%3,added/3);
categories.Add(grid);
categories.Add(Ui.Text(selected.Length>0?"已选:"+choices.First(r=>r.Text("code")==selected).Text("label"):choices.Count>0?"请选择一个财务分类":"暂无启用分类,请在管理分类中新增或启用。",11,Ui.Muted));
}
private async Task Save()
{
var category=selections.GetValueOrDefault(direction,"");if(category.Length==0)throw new ArgumentException("请选择财务分类");
var row=fields.Read();row["type"]=direction;row["category"]=category;
await Task.Run(()=>service.Book(row));await DisplayAlertAsync("已保存","收支已计入财务流水。","完成");await Navigation.PopAsync();
}
}
public sealed class FinanceCategoryPage : ContentPage
{
private readonly BusinessService service;
private readonly VerticalStackLayout body=Ui.Stack();
private string direction;
public FinanceCategoryPage(BusinessService service,string direction="INCOME")
{
this.service=service;this.direction=direction;Title="财务分类";body.Padding=18;Content=new ScrollView{Content=body};
}
protected override void OnAppearing(){base.OnAppearing();Render();}
private void Render()
{
body.Clear();var rows=FinanceCategories.Read(service.Db,direction,false);
var tabs=new Grid{ColumnDefinitions=[new(GridLength.Star),new(GridLength.Star)],ColumnSpacing=8};
tabs.Add(Ui.Button("收入分类",()=>{direction="INCOME";Render();return Task.CompletedTask;},direction=="INCOME"));
tabs.Add(Ui.Button("支出分类",()=>{direction="EXPENSE";Render();return Task.CompletedTask;},direction=="EXPENSE"),1);body.Add(tabs);
body.Add(Ui.Spread(Ui.Text(rows.Count+" 个分类",12,Ui.Muted),Ui.Button("+ 新增分类",()=>Navigation.PushAsync(new FinanceCategoryEditor(service,direction)),false)));
body.Add(Ui.Text("停用后不再供手工记账选择,已有流水继续保留。",12,Ui.Muted));
foreach(var row in rows)
{
var enabled=new Switch{IsToggled=row.Int("enabled")==1,OnColor=Ui.Bronze};bool updating=false;
enabled.Toggled+=async(_,e)=>
{
if(updating)return;enabled.IsEnabled=false;
try{service.EnableFinanceCategory(row.Id,e.Value);row["enabled"]=e.Value?1:0;}
catch(Exception ex){updating=true;enabled.IsToggled=row.Int("enabled")==1;updating=false;await DisplayAlertAsync("操作未完成",ex.Message,"知道了");}
finally{enabled.IsEnabled=true;}
};
var name=Ui.Text(row.Text("label"),15,bold:true);name.MaxLines=2;
var card=Ui.Stack(Ui.Spread(name,enabled),Ui.Spread(Ui.Text("排序 "+row.Int("sortOrder"),11,Ui.Muted),Ui.Button("编辑",()=>Navigation.PushAsync(new FinanceCategoryEditor(service,direction,row)),false)));card.Spacing=6;
body.Add(Ui.Card(card,padding:new Thickness(14,10)));
}
}
}
public sealed class FinanceCategoryEditor : ContentPage
{
public FinanceCategoryEditor(BusinessService service,string direction,DbRow? initial=null,Action<string>? created=null)
{
Title=initial is null?"新增财务分类":"编辑财务分类";
var row=initial is null?new DbRow{["type"]=direction,["enabled"]=1,["sortOrder"]=0}:new DbRow(initial);
var fields=new FieldSet(row,[new("label","分类名称",Required:true),new("sortOrder","排序",FieldKind.Number,true),new("enabled","启用",FieldKind.Toggle)]);
((Entry)fields.Inputs["label"]).MaxLength=30;
var body=Ui.Stack(Ui.Card(Ui.Stack(Ui.Pill(direction=="INCOME"?"收入分类":"支出分类"),fields.View)),Ui.Button("保存分类",async()=>
{
var value=fields.Read();var saved=service.SaveFinanceCategory(value);created?.Invoke(service.Db.Find("FinanceCategory",saved)!.Text("code"));await DisplayAlertAsync("已保存","分类已保存。","完成");await Navigation.PopAsync();
}));body.Padding=18;Content=new ScrollView{Content=body};
}
}
using JiDangJia.Core;
namespace JiDangJia.Views;
public sealed class ManualFinancePage : ContentPage
{
private readonly BusinessService service;
private readonly FieldSet fields;
private readonly VerticalStackLayout categories=Ui.Stack();
private readonly Button income,expense;
private readonly Dictionary<string,string> selections=new();
private string direction="EXPENSE";
public ManualFinancePage(BusinessService service)
{
this.service=service;Title="手工记账";
fields=new(new(){["occurDate"]=Display.Day(DateTime.Today),["operatorId"]=service.Owner.Id},
[new("amount","金额(元)",FieldKind.Number,true),new("occurDate","发生日期",FieldKind.Date,true),new("remark","备注",FieldKind.Notes)]);
income=Ui.Button("收入",()=>Select("INCOME"),false);expense=Ui.Button("支出",()=>Select("EXPENSE"));
var directions=new Grid{ColumnDefinitions=[new(GridLength.Star),new(GridLength.Star)],ColumnSpacing=8};directions.Add(income);directions.Add(expense,1);
var body=Ui.Stack(directions,Ui.Card(categories),Ui.Card(fields.View),
Ui.Text("资方利息可在资金成本页面结算。已自动生成的流水,请勿再次手工记账。",11,Ui.Muted),Ui.Button("确认记账",Save));
body.Padding=new Thickness(18,16,18,28);Content=new ScrollView{Content=body};
}
protected override void OnAppearing(){base.OnAppearing();RenderCategories();}
private Task Select(string value){direction=value;RenderCategories();return Task.CompletedTask;}
private void RenderCategories()
{
income.Background=direction=="INCOME"?Ui.GoldBrush:new SolidColorBrush(Ui.Cream);income.TextColor=direction=="INCOME"?Ui.Ink:Ui.Bronze;
expense.Background=direction=="EXPENSE"?Ui.GoldBrush:new SolidColorBrush(Ui.Cream);expense.TextColor=direction=="EXPENSE"?Ui.Ink:Ui.Bronze;
var choices=FinanceCategories.Read(service.Db,direction);var selected=selections.GetValueOrDefault(direction,"");
if(!choices.Any(r=>r.Text("code")==selected)){selected="";selections.Remove(direction);}
categories.Clear();categories.Add(Ui.Section("财务分类 *"));
var manage=Ui.Button("管理分类 · 修改 / 停用",()=>Navigation.PushAsync(new FinanceCategoryPage(service,direction)),false);manage.HeightRequest=42;categories.Add(manage);
var grid=new Grid{ColumnDefinitions=[new(GridLength.Star),new(GridLength.Star),new(GridLength.Star)],ColumnSpacing=6,RowSpacing=6};
for(int i=0;i<choices.Count;i++)
{
if(i%3==0)grid.RowDefinitions.Add(new(GridLength.Auto));
var row=choices[i];var code=row.Text("code");
var button=Ui.Button(row.Text("label"),()=>{selections[direction]=code;RenderCategories();return Task.CompletedTask;},selected==code);
button.HeightRequest=40;button.FontSize=12;button.Padding=new Thickness(5,0);button.CornerRadius=10;button.LineBreakMode=LineBreakMode.TailTruncation;
SemanticProperties.SetDescription(button,row.Text("label")+(selected==code?",已选择":""));grid.Add(button,i%3,i/3);
}
int added=choices.Count;if(added%3==0)grid.RowDefinitions.Add(new(GridLength.Auto));
var add=Ui.Button("+ 新增分类",()=>Navigation.PushAsync(new FinanceCategoryEditor(service,direction,created:code=>selections[direction]=code)),false);add.HeightRequest=40;add.FontSize=12;add.Padding=new Thickness(3,0);add.CornerRadius=10;add.BorderColor=Ui.Gold;add.BorderWidth=1;grid.Add(add,added%3,added/3);
categories.Add(grid);
categories.Add(Ui.Text(selected.Length>0?"已选:"+choices.First(r=>r.Text("code")==selected).Text("label"):choices.Count>0?"请选择一个财务分类":"暂无启用分类,请在管理分类中新增或启用。",11,Ui.Muted));
}
private async Task Save()
{
var category=selections.GetValueOrDefault(direction,"");if(category.Length==0)throw new ArgumentException("请选择财务分类");
var row=fields.Read();row["type"]=direction;row["category"]=category;
await Task.Run(()=>service.Book(row));await DisplayAlertAsync("已保存","收支已计入财务流水。","完成");await Navigation.PopAsync();
}
}
public sealed class FinanceCategoryPage : ContentPage
{
private readonly BusinessService service;
private readonly VerticalStackLayout body=Ui.Stack();
private string direction;
public FinanceCategoryPage(BusinessService service,string direction="INCOME")
{
this.service=service;this.direction=direction;Title="财务分类";body.Padding=18;Content=new ScrollView{Content=body};
}
protected override void OnAppearing(){base.OnAppearing();Render();}
private void Render()
{
body.Clear();var rows=FinanceCategories.Read(service.Db,direction,false);
var tabs=new Grid{ColumnDefinitions=[new(GridLength.Star),new(GridLength.Star)],ColumnSpacing=8};
tabs.Add(Ui.Button("收入分类",()=>{direction="INCOME";Render();return Task.CompletedTask;},direction=="INCOME"));
tabs.Add(Ui.Button("支出分类",()=>{direction="EXPENSE";Render();return Task.CompletedTask;},direction=="EXPENSE"),1);body.Add(tabs);
body.Add(Ui.Spread(Ui.Text(rows.Count+" 个分类",12,Ui.Muted),Ui.Button("+ 新增分类",()=>Navigation.PushAsync(new FinanceCategoryEditor(service,direction)),false)));
body.Add(Ui.Text("停用后不再供手工记账选择,已有流水继续保留。",12,Ui.Muted));
foreach(var row in rows)
{
var enabled=new Switch{IsToggled=row.Int("enabled")==1,OnColor=Ui.Bronze};bool updating=false;
enabled.Toggled+=async(_,e)=>
{
if(updating)return;enabled.IsEnabled=false;
try{service.EnableFinanceCategory(row.Id,e.Value);row["enabled"]=e.Value?1:0;}
catch(Exception ex){updating=true;enabled.IsToggled=row.Int("enabled")==1;updating=false;await DisplayAlertAsync("操作未完成",ex.Message,"知道了");}
finally{enabled.IsEnabled=true;}
};
var name=Ui.Text(row.Text("label"),15,bold:true);name.MaxLines=2;
var card=Ui.Stack(Ui.Spread(name,enabled),Ui.Spread(Ui.Text("排序 "+row.Int("sortOrder"),11,Ui.Muted),Ui.Button("编辑",()=>Navigation.PushAsync(new FinanceCategoryEditor(service,direction,row)),false)));card.Spacing=6;
body.Add(Ui.Card(card,padding:new Thickness(14,10)));
}
}
}
public sealed class FinanceCategoryEditor : ContentPage
{
public FinanceCategoryEditor(BusinessService service,string direction,DbRow? initial=null,Action<string>? created=null)
{
Title=initial is null?"新增财务分类":"编辑财务分类";
var row=initial is null?new DbRow{["type"]=direction,["enabled"]=1,["sortOrder"]=0}:new DbRow(initial);
var fields=new FieldSet(row,[new("label","分类名称",Required:true),new("sortOrder","排序",FieldKind.Number,true),new("enabled","启用",FieldKind.Toggle)]);
((Entry)fields.Inputs["label"]).MaxLength=30;
var body=Ui.Stack(Ui.Card(Ui.Stack(Ui.Pill(direction=="INCOME"?"收入分类":"支出分类"),fields.View)),Ui.Button("保存分类",async()=>
{
var value=fields.Read();var saved=service.SaveFinanceCategory(value);created?.Invoke(service.Db.Find("FinanceCategory",saved)!.Text("code"));await DisplayAlertAsync("已保存","分类已保存。","完成");await Navigation.PopAsync();
}));body.Padding=18;Content=new ScrollView{Content=body};
}
}