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 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{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? 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}; } }