using JiDangJia.Services;
using System.Text;
using System.Text.Json;
using JiDangJia.Core;
namespace JiDangJia.Views;
public sealed class AdminPage : ContentPage
{
private readonly AuthClient auth=AuthClient.Current;
private readonly VerticalStackLayout list=Ui.Stack();
private readonly VerticalStackLayout metrics=Ui.Stack();
private readonly Entry search=new(){Placeholder="搜索账号 / 批次 / 卡密",ClearButtonVisibility=ClearButtonVisibility.WhileEditing};
private JsonDocument? overview;
private string section="cards";
private int page;
public AdminPage()
{
if(!auth.IsAdmin)throw new InvalidOperationException("需要管理员权限");Title=AppProduct.Name+"管理后台";
var body=Ui.Stack(Ui.Card(Ui.Stack(Ui.Row(Ui.Icon("shield",Ui.Gold,30),Ui.Stack(Ui.Text(AppProduct.Name+"管理中心",21,bold:true),Ui.Text("管理员:"+auth.Name,11,Ui.Muted))))),metrics,Ui.Button("+ 生成并分发卡密",()=>Navigation.PushAsync(new IssueCardsPage(Load))),Ui.Card(search,13,new Thickness(12,0),Ui.Paper));body.Padding=18;
#if !COMMUNITY_APP
body.Insert(3,Ui.Button("首页广告管理",()=>Navigation.PushAsync(new ContentAdminPage()),false));
#endif
body.Insert(3,Ui.Button("客服与反馈入口管理",()=>Navigation.PushAsync(new SupportAdminPage()),false));
body.Insert(3,Ui.Button("关于我们与备案信息",()=>Navigation.PushAsync(new AboutAdminPage()),false));
body.Insert(3,Ui.Button("卡密购买订单",()=>Navigation.PushAsync(new PurchaseAdminPage()),false));
body.Insert(3,Ui.Button("卡密价格管理",()=>Navigation.PushAsync(new PurchasePricesAdminPage()),false));
#if COMMUNITY_APP
body.Insert(3,Ui.Button("社群认证、私信审核与举报",()=>Navigation.PushAsync(new SafetyAdminPage()),false));
#endif
body.Insert(3,Ui.Button("APP 版本更新管理",()=>Navigation.PushAsync(new AppUpdateAdminPage()),false));
body.Insert(3,Ui.Button("用户协议与隐私政策管理",()=>Navigation.PushAsync(new LegalAdminPage()),false));
#if COMMUNITY_APP
body.Insert(4,Ui.Button("社群风险提示与官方推荐",()=>Navigation.PushAsync(new CommunityAdminPage()),false));
#endif
var tabs=new HorizontalStackLayout {Spacing=8};foreach(var pair in new[]{("cards","卡密"),("accounts","账户"),("licenses","授权"),("audit","日志")}){string key=pair.Item1;tabs.Add(Ui.Button(pair.Item2,()=> {section=key;page=0;Render();return Task.CompletedTask;},false));}body.Add(new ScrollView {Content=tabs,Orientation=ScrollOrientation.Horizontal,HorizontalScrollBarVisibility=ScrollBarVisibility.Never});body.Add(list);body.Add(Ui.Button("刷新后台数据",Load,false));Content=new ScrollView {Content=body};search.TextChanged+=(_,_)=>{page=0;Render();};
}
protected override async void OnAppearing() {base.OnAppearing();try{await Load();}catch(Exception ex){await DisplayAlertAsync("无法加载",ex.Message,"知道了");}}
private async Task Load(){var loaded=await auth.AdminRequest("/overview");overview?.Dispose();overview=loaded;Render();}
private void Render()
{
if(overview is null)return;list.Clear();metrics.Clear();var root=overview.RootElement;
var cards=root.GetProperty("cards").EnumerateArray().ToList();var accounts=root.GetProperty("accounts").EnumerateArray().ToList();var licenses=root.GetProperty("licenses").EnumerateArray().ToList();
var stats=new Grid {ColumnDefinitions=[new(GridLength.Star),new(GridLength.Star)],ColumnSpacing=10};stats.Add(Ui.Card(Ui.Stack(Ui.Text("用户账户",11,Ui.Muted),Ui.Text(accounts.Count(a=>a.GetProperty("role").GetString()=="user").ToString(),26,Ui.Bronze,true)),15,new Thickness(16),Ui.Cream));stats.Add(Ui.Card(Ui.Stack(Ui.Text("可分发卡密",11,Ui.Muted),Ui.Text(cards.Count(c=>c.GetProperty("active").GetInt32()==1&&c.GetProperty("usedBy").ValueKind==JsonValueKind.Null&&c.GetProperty("expiresAt").GetDateTime().ToUniversalTime()>DateTime.UtcNow).ToString(),26,Ui.Bronze,true)),15,new Thickness(16)),1);metrics.Add(stats);
var records=root.GetProperty(section).EnumerateArray().Where(r=>string.IsNullOrWhiteSpace(search.Text)||r.EnumerateObject().Any(p=>p.Value.ToString().Contains(search.Text,StringComparison.OrdinalIgnoreCase))).ToList();list.Add(Ui.Text(records.Count+" 条记录",11,Ui.Muted));
if(records.Count==0)list.Add(Ui.Card(Ui.Empty("暂无记录","生成卡密后可分发给门店用户")));
page=Math.Min(page,Math.Max(0,(records.Count-1)/40));
foreach(var record in records.Skip(page*40).Take(40))
{
var r=DbRow.FromJson(record);var id=r.Id;bool active=r.Int("active")==1;var card=Ui.Stack();
if(section=="cards")
{
bool used=r.Text("usedBy").Length>0;card.Add(Ui.Spread(Ui.Text(r.Text("maskedCode"),16,bold:true),Ui.Pill(!active?"已停用":used?"已兑换":"待分发")));
card.Add(Ui.Text(r.Text("batch")+" · "+(r.Int("permanent")==1?"永久":r.Text("fixedUntil").Length>0?"指定到期 "+r.Date("fixedUntil").ToLocalTime().ToString("yyyy-MM-dd"):r.Text("days")+" 天"),12,Ui.Muted));card.Add(Ui.Text("兑换用户:"+r.Text("usedUsername","—"),12,Ui.Muted));card.Add(Ui.Text("兑换有效期:"+r.Date("expiresAt").ToLocalTime().ToString("yyyy-MM-dd"),11,Ui.Muted));
card.Add(Ui.Button(active?"停用卡密":"启用卡密",async()=> {if(await DisplayAlertAsync("确认操作","停用卡密会禁止后续兑换;已有授权可在授权列表单独撤销。","确认","取消")){using var d=await auth.AdminRequest("/cards/"+id+"/status",new{active=!active});await Load();}},false));
}
else if(section=="accounts")
{
card.Add(Ui.Spread(Ui.Text(r.Text("username"),17,bold:true),Ui.Pill(r.Text("role")=="admin"?"管理员":active?"正常":"停用")));card.Add(Ui.Text(r.Text("name"),12,Ui.Muted));
if(r.Text("role")!="admin") {
#if COMMUNITY_APP
card.Add(Ui.Text("认证:"+CommunitySafetyUi.Status(r.Text("identityStatus"))+" · 手机号:"+r.Text("phone"),12,Ui.Muted));card.Add(Ui.Text(r.Int("communityBanned")==1?"社群:已禁言"+(r.Text("communityBanUntil").Length==0?"(永久)":" · "+r.Text("communityBanUntil")):"社群:正常",12,Ui.Bronze));card.Add(Ui.Button("社群禁言设置",()=>Navigation.PushAsync(new CommunityMutePage(r)),false));
#endif
card.Add(Ui.Button(active?"停用账户":"启用账户",async()=> {if(await DisplayAlertAsync("账户状态","确认更新此账户状态?","确认","取消")){using var d=await auth.AdminRequest("/accounts/"+id+"/status",new{active=!active});await Load();}},false));card.Add(Ui.Button("重置用户密码",async()=> {var password=await PasswordDialog.Ask(this,"重置密码","为此用户设置新的密码(至少 8 位)");if(password is null)return;using var d=await auth.AdminRequest("/accounts/"+id+"/reset-password",new{password});await DisplayAlertAsync("已重置","该用户需要使用新密码重新登录。","完成");},false));}
}
else if(section=="licenses")
{
card.Add(Ui.Spread(Ui.Text(r.Text("username"),17,bold:true),Ui.Pill(!active?"已撤销":r.Date("expiresAt").ToUniversalTime()<DateTime.UtcNow?"已到期":"有效")));card.Add(Ui.Text(r.Int("permanent")==1?"永久授权":"有效至 "+r.Date("expiresAt").ToLocalTime().ToString("yyyy-MM-dd HH:mm"),12,Ui.Bronze));card.Add(Ui.Text("设备 "+r.Text("deviceId"),10,Ui.Muted));
card.Add(Ui.Button(active?"撤销设备授权":"恢复设备授权",async()=> {if(await DisplayAlertAsync("授权状态","客户端下次联网校验会同步此变更。","确认","取消")){using var d=await auth.AdminRequest("/licenses/"+id+"/status",new{active=!active});await Load();}},false));
}
else {card.Add(Ui.Text(Display.Action(r.Text("action")),15,bold:true));card.Add(Ui.Text(r.Text("actorName")+" · "+r.Date("createdAt").ToLocalTime().ToString("MM-dd HH:mm"),11,Ui.Muted));card.Add(Ui.Text(r.Text("detail"),11,Ui.Muted));}
list.Add(Ui.Card(card));
}
if(records.Count>40)list.Add(Ui.Spread(Ui.Button("上一页",()=>{page=Math.Max(0,page-1);Render();return Task.CompletedTask;},false),Ui.Button("下一页",()=>{page=Math.Min((records.Count-1)/40,page+1);Render();return Task.CompletedTask;},false)));
}
}
public sealed class IssueCardsPage : ContentPage
{
public IssueCardsPage(Func<Task> refresh)
{
Title="生成与分发卡密";var fields=new FieldSet(new DbRow { ["count"]=10,["days"]=365,["validDays"]=365,["batch"]="门店授权-"+DateTime.Now.ToString("yyyyMMdd") },[new("batch","批次名称",Required:true),new("count","生成数量(1–1000)",FieldKind.Number,true),new("days","自定义授权天数(1–36500)",FieldKind.Number),new("validDays","兑换有效天数",FieldKind.Number,true)]);
var mode=new Picker{Title="授权时间",ItemsSource=new[]{"自定义天数","指定到期日期","永久"},SelectedIndex=0};var until=new DatePicker{Date=DateTime.Today.AddYears(1),MinimumDate=DateTime.Today.AddDays(1)};until.IsVisible=false;
mode.SelectedIndexChanged+=(_,_)=>{fields.SetVisible("days",mode.SelectedIndex==0);until.IsVisible=mode.SelectedIndex==1;};
var output=Ui.Stack();var body=Ui.Stack(Ui.Card(Ui.Stack(Ui.Section("卡密参数"),mode,until,fields.View)),Ui.Text("授权时间可以永久、自定义天数或指定到期日;兑换有效期是未使用卡密的兑换期限。完整卡密只在生成后显示,请及时保存。",12,Ui.Muted),output);body.Padding=18;
var button=Ui.Button("生成卡密",async()=>
{
var row=fields.Read();using var response=await AuthClient.Current.AdminRequest("/cards",new{count=row.Int("count"),days=mode.SelectedIndex==0?row.Int("days"):0,validDays=row.Int("validDays"),batch=row.Text("batch"),permanent=mode.SelectedIndex==2,until=mode.SelectedIndex==1?(DateTimeOffset?)new DateTimeOffset(until.Date!.Value.Date.AddDays(1).AddTicks(-1)):null});var cards=response.RootElement.GetProperty("cards").EnumerateArray().Select(DbRow.FromJson).ToList();output.Clear();
string Duration(DbRow c)=>c.Int("permanent")==1?"永久":c.Text("until").Length>0?"到期 "+c.Date("until").ToLocalTime().ToString("yyyy-MM-dd"):c.Text("days")+" 天";
string Csv(string value)=>"\""+value.Replace("\"","\"\"")+"\"";
var csv="卡密,授权时间,批次\r\n"+string.Join("\r\n",cards.Select(c=>string.Join(",",new[]{c.Text("code"),Duration(c),c.Text("batch")}.Select(Csv))));
var path=AccountDataClear.CacheFile("寄当家-卡密-"+DateTime.Now.ToString("yyyyMMdd-HHmmss")+".csv");await File.WriteAllTextAsync(path,csv,new UTF8Encoding(true));
output.Add(Ui.Card(Ui.Stack(Ui.Section("已生成 "+cards.Count+" 张卡密"),Ui.Button("导出 / 分发 CSV",()=>Share.RequestAsync(new ShareFileRequest("寄当家卡密分发",new ShareFile(path)))),Ui.Button("复制所有卡密",()=>Clipboard.Default.SetTextAsync(string.Join("\n",cards.Select(c=>c.Text("code")))),false))));
foreach(var c in cards.Take(100))output.Add(Ui.Card(Ui.Spread(Ui.Stack(Ui.Text(c.Text("code"),12,bold:true),Ui.Text(Duration(c)+" · "+c.Text("batch"),10,Ui.Muted)),Ui.Button("复制",()=>Clipboard.Default.SetTextAsync(c.Text("code")),false))));await refresh();
});body.Insert(2,button);Content=new ScrollView {Content=body};
}
}
using JiDangJia.Services;
using System.Text;
using System.Text.Json;
using JiDangJia.Core;
namespace JiDangJia.Views;
public sealed class AdminPage : ContentPage
{
private readonly AuthClient auth=AuthClient.Current;
private readonly VerticalStackLayout list=Ui.Stack();
private readonly VerticalStackLayout metrics=Ui.Stack();
private readonly Entry search=new(){Placeholder="搜索账号 / 批次 / 卡密",ClearButtonVisibility=ClearButtonVisibility.WhileEditing};
private JsonDocument? overview;
private string section="cards";
private int page;
public AdminPage()
{
if(!auth.IsAdmin)throw new InvalidOperationException("需要管理员权限");Title=AppProduct.Name+"管理后台";
var body=Ui.Stack(Ui.Card(Ui.Stack(Ui.Row(Ui.Icon("shield",Ui.Gold,30),Ui.Stack(Ui.Text(AppProduct.Name+"管理中心",21,bold:true),Ui.Text("管理员:"+auth.Name,11,Ui.Muted))))),metrics,Ui.Button("+ 生成并分发卡密",()=>Navigation.PushAsync(new IssueCardsPage(Load))),Ui.Card(search,13,new Thickness(12,0),Ui.Paper));body.Padding=18;
#if !COMMUNITY_APP
body.Insert(3,Ui.Button("首页广告管理",()=>Navigation.PushAsync(new ContentAdminPage()),false));
#endif
body.Insert(3,Ui.Button("客服与反馈入口管理",()=>Navigation.PushAsync(new SupportAdminPage()),false));
body.Insert(3,Ui.Button("关于我们与备案信息",()=>Navigation.PushAsync(new AboutAdminPage()),false));
body.Insert(3,Ui.Button("卡密购买订单",()=>Navigation.PushAsync(new PurchaseAdminPage()),false));
body.Insert(3,Ui.Button("卡密价格管理",()=>Navigation.PushAsync(new PurchasePricesAdminPage()),false));
#if COMMUNITY_APP
body.Insert(3,Ui.Button("社群认证、私信审核与举报",()=>Navigation.PushAsync(new SafetyAdminPage()),false));
#endif
body.Insert(3,Ui.Button("APP 版本更新管理",()=>Navigation.PushAsync(new AppUpdateAdminPage()),false));
body.Insert(3,Ui.Button("用户协议与隐私政策管理",()=>Navigation.PushAsync(new LegalAdminPage()),false));
#if COMMUNITY_APP
body.Insert(4,Ui.Button("社群风险提示与官方推荐",()=>Navigation.PushAsync(new CommunityAdminPage()),false));
#endif
var tabs=new HorizontalStackLayout {Spacing=8};foreach(var pair in new[]{("cards","卡密"),("accounts","账户"),("licenses","授权"),("audit","日志")}){string key=pair.Item1;tabs.Add(Ui.Button(pair.Item2,()=> {section=key;page=0;Render();return Task.CompletedTask;},false));}body.Add(new ScrollView {Content=tabs,Orientation=ScrollOrientation.Horizontal,HorizontalScrollBarVisibility=ScrollBarVisibility.Never});body.Add(list);body.Add(Ui.Button("刷新后台数据",Load,false));Content=new ScrollView {Content=body};search.TextChanged+=(_,_)=>{page=0;Render();};
}
protected override async void OnAppearing() {base.OnAppearing();try{await Load();}catch(Exception ex){await DisplayAlertAsync("无法加载",ex.Message,"知道了");}}
private async Task Load(){var loaded=await auth.AdminRequest("/overview");overview?.Dispose();overview=loaded;Render();}
private void Render()
{
if(overview is null)return;list.Clear();metrics.Clear();var root=overview.RootElement;
var cards=root.GetProperty("cards").EnumerateArray().ToList();var accounts=root.GetProperty("accounts").EnumerateArray().ToList();var licenses=root.GetProperty("licenses").EnumerateArray().ToList();
var stats=new Grid {ColumnDefinitions=[new(GridLength.Star),new(GridLength.Star)],ColumnSpacing=10};stats.Add(Ui.Card(Ui.Stack(Ui.Text("用户账户",11,Ui.Muted),Ui.Text(accounts.Count(a=>a.GetProperty("role").GetString()=="user").ToString(),26,Ui.Bronze,true)),15,new Thickness(16),Ui.Cream));stats.Add(Ui.Card(Ui.Stack(Ui.Text("可分发卡密",11,Ui.Muted),Ui.Text(cards.Count(c=>c.GetProperty("active").GetInt32()==1&&c.GetProperty("usedBy").ValueKind==JsonValueKind.Null&&c.GetProperty("expiresAt").GetDateTime().ToUniversalTime()>DateTime.UtcNow).ToString(),26,Ui.Bronze,true)),15,new Thickness(16)),1);metrics.Add(stats);
var records=root.GetProperty(section).EnumerateArray().Where(r=>string.IsNullOrWhiteSpace(search.Text)||r.EnumerateObject().Any(p=>p.Value.ToString().Contains(search.Text,StringComparison.OrdinalIgnoreCase))).ToList();list.Add(Ui.Text(records.Count+" 条记录",11,Ui.Muted));
if(records.Count==0)list.Add(Ui.Card(Ui.Empty("暂无记录","生成卡密后可分发给门店用户")));
page=Math.Min(page,Math.Max(0,(records.Count-1)/40));
foreach(var record in records.Skip(page*40).Take(40))
{
var r=DbRow.FromJson(record);var id=r.Id;bool active=r.Int("active")==1;var card=Ui.Stack();
if(section=="cards")
{
bool used=r.Text("usedBy").Length>0;card.Add(Ui.Spread(Ui.Text(r.Text("maskedCode"),16,bold:true),Ui.Pill(!active?"已停用":used?"已兑换":"待分发")));
card.Add(Ui.Text(r.Text("batch")+" · "+(r.Int("permanent")==1?"永久":r.Text("fixedUntil").Length>0?"指定到期 "+r.Date("fixedUntil").ToLocalTime().ToString("yyyy-MM-dd"):r.Text("days")+" 天"),12,Ui.Muted));card.Add(Ui.Text("兑换用户:"+r.Text("usedUsername","—"),12,Ui.Muted));card.Add(Ui.Text("兑换有效期:"+r.Date("expiresAt").ToLocalTime().ToString("yyyy-MM-dd"),11,Ui.Muted));
card.Add(Ui.Button(active?"停用卡密":"启用卡密",async()=> {if(await DisplayAlertAsync("确认操作","停用卡密会禁止后续兑换;已有授权可在授权列表单独撤销。","确认","取消")){using var d=await auth.AdminRequest("/cards/"+id+"/status",new{active=!active});await Load();}},false));
}
else if(section=="accounts")
{
card.Add(Ui.Spread(Ui.Text(r.Text("username"),17,bold:true),Ui.Pill(r.Text("role")=="admin"?"管理员":active?"正常":"停用")));card.Add(Ui.Text(r.Text("name"),12,Ui.Muted));
if(r.Text("role")!="admin") {
#if COMMUNITY_APP
card.Add(Ui.Text("认证:"+CommunitySafetyUi.Status(r.Text("identityStatus"))+" · 手机号:"+r.Text("phone"),12,Ui.Muted));card.Add(Ui.Text(r.Int("communityBanned")==1?"社群:已禁言"+(r.Text("communityBanUntil").Length==0?"(永久)":" · "+r.Text("communityBanUntil")):"社群:正常",12,Ui.Bronze));card.Add(Ui.Button("社群禁言设置",()=>Navigation.PushAsync(new CommunityMutePage(r)),false));
#endif
card.Add(Ui.Button(active?"停用账户":"启用账户",async()=> {if(await DisplayAlertAsync("账户状态","确认更新此账户状态?","确认","取消")){using var d=await auth.AdminRequest("/accounts/"+id+"/status",new{active=!active});await Load();}},false));card.Add(Ui.Button("重置用户密码",async()=> {var password=await PasswordDialog.Ask(this,"重置密码","为此用户设置新的密码(至少 8 位)");if(password is null)return;using var d=await auth.AdminRequest("/accounts/"+id+"/reset-password",new{password});await DisplayAlertAsync("已重置","该用户需要使用新密码重新登录。","完成");},false));}
}
else if(section=="licenses")
{
card.Add(Ui.Spread(Ui.Text(r.Text("username"),17,bold:true),Ui.Pill(!active?"已撤销":r.Date("expiresAt").ToUniversalTime()<DateTime.UtcNow?"已到期":"有效")));card.Add(Ui.Text(r.Int("permanent")==1?"永久授权":"有效至 "+r.Date("expiresAt").ToLocalTime().ToString("yyyy-MM-dd HH:mm"),12,Ui.Bronze));card.Add(Ui.Text("设备 "+r.Text("deviceId"),10,Ui.Muted));
card.Add(Ui.Button(active?"撤销设备授权":"恢复设备授权",async()=> {if(await DisplayAlertAsync("授权状态","客户端下次联网校验会同步此变更。","确认","取消")){using var d=await auth.AdminRequest("/licenses/"+id+"/status",new{active=!active});await Load();}},false));
}
else {card.Add(Ui.Text(Display.Action(r.Text("action")),15,bold:true));card.Add(Ui.Text(r.Text("actorName")+" · "+r.Date("createdAt").ToLocalTime().ToString("MM-dd HH:mm"),11,Ui.Muted));card.Add(Ui.Text(r.Text("detail"),11,Ui.Muted));}
list.Add(Ui.Card(card));
}
if(records.Count>40)list.Add(Ui.Spread(Ui.Button("上一页",()=>{page=Math.Max(0,page-1);Render();return Task.CompletedTask;},false),Ui.Button("下一页",()=>{page=Math.Min((records.Count-1)/40,page+1);Render();return Task.CompletedTask;},false)));
}
}
public sealed class IssueCardsPage : ContentPage
{
public IssueCardsPage(Func<Task> refresh)
{
Title="生成与分发卡密";var fields=new FieldSet(new DbRow { ["count"]=10,["days"]=365,["validDays"]=365,["batch"]="门店授权-"+DateTime.Now.ToString("yyyyMMdd") },[new("batch","批次名称",Required:true),new("count","生成数量(1–1000)",FieldKind.Number,true),new("days","自定义授权天数(1–36500)",FieldKind.Number),new("validDays","兑换有效天数",FieldKind.Number,true)]);
var mode=new Picker{Title="授权时间",ItemsSource=new[]{"自定义天数","指定到期日期","永久"},SelectedIndex=0};var until=new DatePicker{Date=DateTime.Today.AddYears(1),MinimumDate=DateTime.Today.AddDays(1)};until.IsVisible=false;
mode.SelectedIndexChanged+=(_,_)=>{fields.SetVisible("days",mode.SelectedIndex==0);until.IsVisible=mode.SelectedIndex==1;};
var output=Ui.Stack();var body=Ui.Stack(Ui.Card(Ui.Stack(Ui.Section("卡密参数"),mode,until,fields.View)),Ui.Text("授权时间可以永久、自定义天数或指定到期日;兑换有效期是未使用卡密的兑换期限。完整卡密只在生成后显示,请及时保存。",12,Ui.Muted),output);body.Padding=18;
var button=Ui.Button("生成卡密",async()=>
{
var row=fields.Read();using var response=await AuthClient.Current.AdminRequest("/cards",new{count=row.Int("count"),days=mode.SelectedIndex==0?row.Int("days"):0,validDays=row.Int("validDays"),batch=row.Text("batch"),permanent=mode.SelectedIndex==2,until=mode.SelectedIndex==1?(DateTimeOffset?)new DateTimeOffset(until.Date!.Value.Date.AddDays(1).AddTicks(-1)):null});var cards=response.RootElement.GetProperty("cards").EnumerateArray().Select(DbRow.FromJson).ToList();output.Clear();
string Duration(DbRow c)=>c.Int("permanent")==1?"永久":c.Text("until").Length>0?"到期 "+c.Date("until").ToLocalTime().ToString("yyyy-MM-dd"):c.Text("days")+" 天";
string Csv(string value)=>"\""+value.Replace("\"","\"\"")+"\"";
var csv="卡密,授权时间,批次\r\n"+string.Join("\r\n",cards.Select(c=>string.Join(",",new[]{c.Text("code"),Duration(c),c.Text("batch")}.Select(Csv))));
var path=AccountDataClear.CacheFile("寄当家-卡密-"+DateTime.Now.ToString("yyyyMMdd-HHmmss")+".csv");await File.WriteAllTextAsync(path,csv,new UTF8Encoding(true));
output.Add(Ui.Card(Ui.Stack(Ui.Section("已生成 "+cards.Count+" 张卡密"),Ui.Button("导出 / 分发 CSV",()=>Share.RequestAsync(new ShareFileRequest("寄当家卡密分发",new ShareFile(path)))),Ui.Button("复制所有卡密",()=>Clipboard.Default.SetTextAsync(string.Join("\n",cards.Select(c=>c.Text("code")))),false))));
foreach(var c in cards.Take(100))output.Add(Ui.Card(Ui.Spread(Ui.Stack(Ui.Text(c.Text("code"),12,bold:true),Ui.Text(Duration(c)+" · "+c.Text("batch"),10,Ui.Muted)),Ui.Button("复制",()=>Clipboard.Default.SetTextAsync(c.Text("code")),false))));await refresh();
});body.Insert(2,button);Content=new ScrollView {Content=body};
}
}