using JiDangJia.Contracts;
using JiDangJia.Core;
using JiDangJia.Services;
using System.Text.Json;
namespace JiDangJia.Views;
public sealed class SupportPage : ContentPage
{
private readonly VerticalStackLayout body=Ui.Stack();
public SupportPage(){Title="客服与反馈";body.Padding=18;Content=new ScrollView{Content=body};Render();}
protected override async void OnAppearing()
{
base.OnAppearing();try{await ContentClient.Refresh(true);Render();}catch(Exception ex){body.Add(Ui.Text("暂时无法更新客服信息:"+ex.Message,12,Ui.Muted));}
}
private void Render()
{
body.Clear();body.Add(Ui.Section("客服与反馈"));
foreach(var entry in (ContentClient.Current?.Support??SupportCatalog.Defaults).Where(e=>e.Enabled).OrderBy(e=>e.SortOrder))
{
var card=Ui.Stack(Ui.Section(entry.Title),Ui.Text(entry.Description,13,Ui.Muted));
if(entry.Phone.Length>0)
{
card.Add(Ui.Text("客服电话:"+entry.Phone,17,Ui.Bronze,true));
card.Add(Ui.Spread(Ui.Button("复制电话",()=>Clipboard.Default.SetTextAsync(entry.Phone),false),Ui.Button("拨打电话",()=>{PhoneDialer.Default.Open(entry.Phone);return Task.CompletedTask;})));
}
if(entry.QrImage.Length>0)card.Add(new Image{Source=MediaFiles.Source(entry.QrImage),HeightRequest=220,Aspect=Aspect.AspectFit});
if(entry.Link.Length>0)card.Add(Ui.Button("打开"+entry.Title,()=>Browser.OpenAsync(entry.Link,BrowserLaunchMode.SystemPreferred)));
if(entry.Phone.Length==0&&entry.QrImage.Length==0&&entry.Link.Length==0)card.Add(Ui.Text("联系方式待公布",12,Ui.Bronze));
body.Add(Ui.Card(card));
}
body.Add(Ui.Button("使用帮助",()=>Navigation.PushAsync(new HelpPage()),false));
}
}
#if !COMMUNITY_APP
public sealed class BusinessToolsPage : ContentPage
{
public BusinessToolsPage()
{
Title="经营工具";var body=Ui.Stack(Ui.Section("经营工具"),Ui.Text("服务接入后将在这里开放。",13,Ui.Muted));body.Padding=18;
foreach(var tool in new[]{("特惠发件","archive"),("货运保险","shield"),("其他工具","settings")})
{
var name=tool.Item1;body.Add(Ui.Tap(Ui.Card(Ui.Spread(Ui.Row(Ui.Icon(tool.Item2,Ui.Bronze,24),Ui.Text(name,17,bold:true)),Ui.Pill("待上架"))),()=>DisplayAlertAsync(name,"待上架,服务开放后可在这里使用。","知道了"),name));
}
Content=new ScrollView{Content=body};
}
}
#endif
public sealed class SupportAdminPage : ContentPage
{
private readonly VerticalStackLayout list=Ui.Stack();
private AppContent? content;
private static readonly JsonSerializerOptions json=new(JsonSerializerDefaults.Web);
public SupportAdminPage()
{
if(!AuthClient.Current.IsAdmin)throw new InvalidOperationException("需要管理员权限");Title="客服与反馈入口管理";
var body=Ui.Stack(Ui.Card(Ui.Stack(Ui.Section("全局客服入口"),Ui.Text("统一配置电话、客服二维码及反馈链接。发布后所有门店可查看,最多 12 个自定义入口。",13,Ui.Muted))),Ui.Button("+ 新增客服入口",()=>Edit(null)),list,Ui.Button("刷新客服配置",Load,false));body.Padding=18;Content=new ScrollView{Content=body};
}
protected override async void OnAppearing(){base.OnAppearing();try{await Load();}catch(Exception ex){await DisplayAlertAsync("无法加载",ex.Message,"知道了");}}
private async Task Load(){using var data=await AuthClient.Current.AdminRequest("/content");content=data.RootElement.Deserialize<AppContent>(json)??throw new InvalidOperationException("后台内容无效");Render();}
private async Task Save(List<SupportEntry> entries)
{
if(content is null)throw new InvalidOperationException("请先刷新后台配置");using var data=await AuthClient.Current.AdminRequest("/support",new UpdateSupportRequest(content.Revision,entries));content=data.RootElement.Deserialize<AppContent>(json)??throw new InvalidOperationException("后台内容无效");ContentClient.Remember(content);Render();
}
private void Render()
{
if(content is null)return;list.Clear();foreach(var entry in content.Support??[])
{
var row=Ui.Stack(Ui.Spread(Ui.Text(entry.Title,17,bold:true),Ui.Pill(entry.Enabled?"已启用":"已停用")),Ui.Text(entry.Description,12,Ui.Muted),Ui.Text(entry.Phone.Length>0?entry.Phone:"暂未配置电话",12,Ui.Bronze));
if(entry.QrImage.Length>0)row.Add(new Image{Source=MediaFiles.Source(entry.QrImage),HeightRequest=100,Aspect=Aspect.AspectFit});
row.Add(Ui.Button("编辑入口",()=>Edit(entry),false));
#if !COMMUNITY_APP
bool fixedEntry=entry.Id is "purchase-contact" or "invoice-contact";
#else
bool fixedEntry=false;
#endif
if(!fixedEntry)row.Add(Ui.Button("删除入口",async()=>{if(await DisplayAlertAsync("删除客服入口","所有门店同步后将不再显示此入口。","删除","取消"))await Save((content.Support??[]).Where(e=>e.Id!=entry.Id).ToList());},false));
else row.Add(Ui.Text("此图片同步到登录页的"+(entry.Id=="purchase-contact"?"扫码联系客服购买":"需要发票")+"入口。",12,Ui.Bronze));list.Add(Ui.Card(row));
}
}
private Task Edit(SupportEntry? entry)
{
if(content is null)throw new InvalidOperationException("请先刷新后台配置");if(entry is null&&(content.Support?.Count??0)>=12)throw new ArgumentException("最多 12 个入口");
return Navigation.PushAsync(new SupportEditor(entry,updated=>Save((content.Support??[]).Where(e=>e.Id!=updated.Id).Append(updated).ToList())));
}
}
public sealed class SupportEditor : ContentPage
{
public SupportEditor(SupportEntry? entry,Func<SupportEntry,Task> publish)
{
if(!AuthClient.Current.IsAdmin)throw new InvalidOperationException("需要管理员权限");Title=entry is null?"新增客服入口":"编辑客服入口";
var row=new DbRow{["title"]=entry?.Title??"",["description"]=entry?.Description??"",["phone"]=entry?.Phone??"",["qr"]=JsonSerializer.Serialize(entry is {QrImage.Length:>0}?new[]{entry.QrImage}:Array.Empty<string>()),["link"]=entry?.Link??"",["enabled"]=entry is null||entry.Enabled?1:0,["sortOrder"]=entry?.SortOrder??0};
var fields=new FieldSet(row,[new("title","入口名称",Required:true),new("description","说明",FieldKind.Notes),new("phone","客服电话"),new("qr","客服二维码(1 张,JPEG / PNG,最多 1 MB)",FieldKind.Photos),new("link","反馈 / 联系 HTTPS 链接"),new("sortOrder","排序",FieldKind.Number,true),new("enabled","启用入口",FieldKind.Toggle)]);
var id=entry?.Id??Guid.NewGuid().ToString("N");var body=Ui.Stack(Ui.Card(fields.View),Ui.Button("发布客服配置",async()=>
{
var value=fields.Read();if(value.Money("sortOrder")%1!=0||value.Money("sortOrder") is <0 or >9999)throw new ArgumentException("排序请填写 0 至 9999 的整数");var photos=MediaFiles.Parse(value.Text("qr"));if(photos.Count>1)throw new ArgumentException("每个入口只可上传 1 张二维码");
await publish(new(id,value.Text("title"),value.Text("description"),value.Text("phone"),photos.FirstOrDefault()??"",value.Text("link"),value.Int("enabled")==1,value.Int("sortOrder")));await DisplayAlertAsync("已发布","各门店联网后会自动同步客服入口。","完成");await Navigation.PopAsync();
}));body.Padding=18;Content=new ScrollView{Content=body};
}
}
using JiDangJia.Contracts;
using JiDangJia.Core;
using JiDangJia.Services;
using System.Text.Json;
namespace JiDangJia.Views;
public sealed class SupportPage : ContentPage
{
private readonly VerticalStackLayout body=Ui.Stack();
public SupportPage(){Title="客服与反馈";body.Padding=18;Content=new ScrollView{Content=body};Render();}
protected override async void OnAppearing()
{
base.OnAppearing();try{await ContentClient.Refresh(true);Render();}catch(Exception ex){body.Add(Ui.Text("暂时无法更新客服信息:"+ex.Message,12,Ui.Muted));}
}
private void Render()
{
body.Clear();body.Add(Ui.Section("客服与反馈"));
foreach(var entry in (ContentClient.Current?.Support??SupportCatalog.Defaults).Where(e=>e.Enabled).OrderBy(e=>e.SortOrder))
{
var card=Ui.Stack(Ui.Section(entry.Title),Ui.Text(entry.Description,13,Ui.Muted));
if(entry.Phone.Length>0)
{
card.Add(Ui.Text("客服电话:"+entry.Phone,17,Ui.Bronze,true));
card.Add(Ui.Spread(Ui.Button("复制电话",()=>Clipboard.Default.SetTextAsync(entry.Phone),false),Ui.Button("拨打电话",()=>{PhoneDialer.Default.Open(entry.Phone);return Task.CompletedTask;})));
}
if(entry.QrImage.Length>0)card.Add(new Image{Source=MediaFiles.Source(entry.QrImage),HeightRequest=220,Aspect=Aspect.AspectFit});
if(entry.Link.Length>0)card.Add(Ui.Button("打开"+entry.Title,()=>Browser.OpenAsync(entry.Link,BrowserLaunchMode.SystemPreferred)));
if(entry.Phone.Length==0&&entry.QrImage.Length==0&&entry.Link.Length==0)card.Add(Ui.Text("联系方式待公布",12,Ui.Bronze));
body.Add(Ui.Card(card));
}
body.Add(Ui.Button("使用帮助",()=>Navigation.PushAsync(new HelpPage()),false));
}
}
#if !COMMUNITY_APP
public sealed class BusinessToolsPage : ContentPage
{
public BusinessToolsPage()
{
Title="经营工具";var body=Ui.Stack(Ui.Section("经营工具"),Ui.Text("服务接入后将在这里开放。",13,Ui.Muted));body.Padding=18;
foreach(var tool in new[]{("特惠发件","archive"),("货运保险","shield"),("其他工具","settings")})
{
var name=tool.Item1;body.Add(Ui.Tap(Ui.Card(Ui.Spread(Ui.Row(Ui.Icon(tool.Item2,Ui.Bronze,24),Ui.Text(name,17,bold:true)),Ui.Pill("待上架"))),()=>DisplayAlertAsync(name,"待上架,服务开放后可在这里使用。","知道了"),name));
}
Content=new ScrollView{Content=body};
}
}
#endif
public sealed class SupportAdminPage : ContentPage
{
private readonly VerticalStackLayout list=Ui.Stack();
private AppContent? content;
private static readonly JsonSerializerOptions json=new(JsonSerializerDefaults.Web);
public SupportAdminPage()
{
if(!AuthClient.Current.IsAdmin)throw new InvalidOperationException("需要管理员权限");Title="客服与反馈入口管理";
var body=Ui.Stack(Ui.Card(Ui.Stack(Ui.Section("全局客服入口"),Ui.Text("统一配置电话、客服二维码及反馈链接。发布后所有门店可查看,最多 12 个自定义入口。",13,Ui.Muted))),Ui.Button("+ 新增客服入口",()=>Edit(null)),list,Ui.Button("刷新客服配置",Load,false));body.Padding=18;Content=new ScrollView{Content=body};
}
protected override async void OnAppearing(){base.OnAppearing();try{await Load();}catch(Exception ex){await DisplayAlertAsync("无法加载",ex.Message,"知道了");}}
private async Task Load(){using var data=await AuthClient.Current.AdminRequest("/content");content=data.RootElement.Deserialize<AppContent>(json)??throw new InvalidOperationException("后台内容无效");Render();}
private async Task Save(List<SupportEntry> entries)
{
if(content is null)throw new InvalidOperationException("请先刷新后台配置");using var data=await AuthClient.Current.AdminRequest("/support",new UpdateSupportRequest(content.Revision,entries));content=data.RootElement.Deserialize<AppContent>(json)??throw new InvalidOperationException("后台内容无效");ContentClient.Remember(content);Render();
}
private void Render()
{
if(content is null)return;list.Clear();foreach(var entry in content.Support??[])
{
var row=Ui.Stack(Ui.Spread(Ui.Text(entry.Title,17,bold:true),Ui.Pill(entry.Enabled?"已启用":"已停用")),Ui.Text(entry.Description,12,Ui.Muted),Ui.Text(entry.Phone.Length>0?entry.Phone:"暂未配置电话",12,Ui.Bronze));
if(entry.QrImage.Length>0)row.Add(new Image{Source=MediaFiles.Source(entry.QrImage),HeightRequest=100,Aspect=Aspect.AspectFit});
row.Add(Ui.Button("编辑入口",()=>Edit(entry),false));
#if !COMMUNITY_APP
bool fixedEntry=entry.Id is "purchase-contact" or "invoice-contact";
#else
bool fixedEntry=false;
#endif
if(!fixedEntry)row.Add(Ui.Button("删除入口",async()=>{if(await DisplayAlertAsync("删除客服入口","所有门店同步后将不再显示此入口。","删除","取消"))await Save((content.Support??[]).Where(e=>e.Id!=entry.Id).ToList());},false));
else row.Add(Ui.Text("此图片同步到登录页的"+(entry.Id=="purchase-contact"?"扫码联系客服购买":"需要发票")+"入口。",12,Ui.Bronze));list.Add(Ui.Card(row));
}
}
private Task Edit(SupportEntry? entry)
{
if(content is null)throw new InvalidOperationException("请先刷新后台配置");if(entry is null&&(content.Support?.Count??0)>=12)throw new ArgumentException("最多 12 个入口");
return Navigation.PushAsync(new SupportEditor(entry,updated=>Save((content.Support??[]).Where(e=>e.Id!=updated.Id).Append(updated).ToList())));
}
}
public sealed class SupportEditor : ContentPage
{
public SupportEditor(SupportEntry? entry,Func<SupportEntry,Task> publish)
{
if(!AuthClient.Current.IsAdmin)throw new InvalidOperationException("需要管理员权限");Title=entry is null?"新增客服入口":"编辑客服入口";
var row=new DbRow{["title"]=entry?.Title??"",["description"]=entry?.Description??"",["phone"]=entry?.Phone??"",["qr"]=JsonSerializer.Serialize(entry is {QrImage.Length:>0}?new[]{entry.QrImage}:Array.Empty<string>()),["link"]=entry?.Link??"",["enabled"]=entry is null||entry.Enabled?1:0,["sortOrder"]=entry?.SortOrder??0};
var fields=new FieldSet(row,[new("title","入口名称",Required:true),new("description","说明",FieldKind.Notes),new("phone","客服电话"),new("qr","客服二维码(1 张,JPEG / PNG,最多 1 MB)",FieldKind.Photos),new("link","反馈 / 联系 HTTPS 链接"),new("sortOrder","排序",FieldKind.Number,true),new("enabled","启用入口",FieldKind.Toggle)]);
var id=entry?.Id??Guid.NewGuid().ToString("N");var body=Ui.Stack(Ui.Card(fields.View),Ui.Button("发布客服配置",async()=>
{
var value=fields.Read();if(value.Money("sortOrder")%1!=0||value.Money("sortOrder") is <0 or >9999)throw new ArgumentException("排序请填写 0 至 9999 的整数");var photos=MediaFiles.Parse(value.Text("qr"));if(photos.Count>1)throw new ArgumentException("每个入口只可上传 1 张二维码");
await publish(new(id,value.Text("title"),value.Text("description"),value.Text("phone"),photos.FirstOrDefault()??"",value.Text("link"),value.Int("enabled")==1,value.Int("sortOrder")));await DisplayAlertAsync("已发布","各门店联网后会自动同步客服入口。","完成");await Navigation.PopAsync();
}));body.Padding=18;Content=new ScrollView{Content=body};
}
}