从零开始搭建Wpf基础10-接入Api数据与获取真实菜单

AIStudio框架汇总及介绍

前言:接入后台数据,前后台实体类都基本一直,稍微各有各的扩展,后台可以采用modelfirst,前言还是采用dbfrist好了,直接生成实体类。

第零步:可能你还没有数据库脚本,项目里提供一个sqlserver的执行语句(以下也是以sqlserver为例),如果你想使用sqlite,那么项目里有加好数据的Api.db,你把连接字符串改成连接Api.db的即可。

第一步:新建实体类项目AIStudio.Wpf.Entity.Models,安装Microsoft.EntityFrameworkCore.SqlServer,Microsoft.EntityFrameworkCore.Tools,Microsoft.EntityFrameworkCore.Design。

然后在程序包控制台输入: Scaffold-DbContext "Server=.;Database=Colder.Admin.AntdVue;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -UseDatabaseNames 生成成功(是不是很省事):

第二步:新增Operator类,保存登录者的信息,并实现接口IOperator。

public class Operator : IOperator
{
    public string UserId { get { return Property?.Id; } }
    /// <summary>
    /// 当前操作者UserName
    /// </summary>
    public string UserName { get; set; }

    public string Avatar { get; set; }

    public Base_User Property { get; set; }

    public List<string> Permissions { get; set; }

    //菜单树
    public ObservableCollection<AMenuItem> MenuItems { get; set; }

    //打平用于查询的菜单
    public ObservableCollection<AMenuItem> SearchMenus { get; set; }

}
public interface IOperator
{
    /// <summary>
    /// 当前操作者UserId
    /// </summary>
    string UserId { get; }
    string UserName { get; set; }
    string Avatar { get; set; }
    /// <summary>
    /// 当前操作者
    /// </summary>
    Base_User Property { get; set; }

    List<string> Permissions { get; set; }

    //菜单树
    ObservableCollection<AMenuItem> MenuItems { get; set; }

    //打平用于查询的菜单
    ObservableCollection<AMenuItem> SearchMenus { get; set; }

    #region 操作方法

    #endregion
}

别忘了在App.xaml.cs注册

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.Register<IDataProvider, ApiDataProvider>();
    containerRegistry.RegisterSingleton<IOperator, Operator>();
}

第三步:将登录信息保存,并用获取到的后台菜单。

 private async void InitMenu()
{
    var userinfo = await _dataProvider.GetData<UserInfoPermissions>("/Base_Manage/Home/GetOperatorInfo");
    if (!userinfo.Success)
    {
        throw new System.Exception(userinfo.Msg);
    }

    _operator.Property = userinfo.Data.UserInfo;
    _operator.Permissions = userinfo.Data.Permissions;
    _operator.Avatar = userinfo.Data.UserInfo.Avatar;

    var menuinfo = await _dataProvider.GetData<List<Base_ActionTree>>("/Base_Manage/Home/GetOperatorMenuList");
    if (!menuinfo.Success)
    {
        throw new System.Exception(menuinfo.Msg);
    }
    BuildMenu(menuinfo.Data);
}

private void BuildMenu(List<Base_ActionTree> base_Actions)
{
    var nodes = base_Actions.Where(p => string.IsNullOrEmpty(p.ParentId));
    foreach (var node in nodes)
    {
        AMenuItem aMenuItem = new AMenuItem() { Glyph = node.Icon, Label = node.Text, Code = node.Url, Type = node.Type, ParentId = node.ParentId, Id = node.Id };
        MenuItems.Add(aMenuItem);
        SubBuildMenu(aMenuItem, node, aMenuItem.Id);
    }
}

private void SubBuildMenu(AMenuItem menuItem, Base_ActionTree parent, string parentid)
{
    if (parent.Children != null)
    {
        foreach (var node in parent.Children)
        {
            AMenuItem aMenuItem = new AMenuItem() { Glyph = node.Icon, Label = node.Text, Code = node.Url, Type = node.Type, ParentId = node.ParentId, Id = node.Id };
            menuItem.AddChildren(aMenuItem);
            SubBuildMenu(aMenuItem, node, aMenuItem.Id);
        }
    }
}

运行,报错。因为需要登录后才能获取菜单,而这个在MainWindow初始化的时候就会执行。但是MainView尚未Loaded,所以我们可以在登录后Loaded的过程中实现获取菜单信息。简单的方法是在MainView.cs里面加Loaded事件,然后传替给MainViewModel进行InitMenu.

第四步:我们借助已经实现好的类库Accelerider.Extensions进行VM内直接获得Loaded方法。

核心代码,在实例化View和ViewModel的时候,将View的Loaded触发ViewModel的Loaded。

public static IViewModelResolver UseDefaultConfigure(this IViewModelResolver @this) => @this
    .IfInheritsFrom<ViewModelBase>((view, viewModel) =>
    {
        viewModel.Dispatcher = view.Dispatcher;
    })
    .IfInheritsFrom<IViewLoadedAndUnloadedAware>((view, viewModel) =>
    {
        view.Loaded += (sender, e) => viewModel.OnLoaded();
        view.Unloaded += (sender, e) => viewModel.OnUnloaded();
    })
    .IfInheritsFrom(typeof(IViewLoadedAndUnloadedAware<>), (view, viewModel, interfaceInstance) =>
    {
        var viewType = view.GetType();
        if (interfaceInstance.GenericArguments.Single() != viewType)
        {
            throw new InvalidOperationException();
        }

        var onLoadedMethod = interfaceInstance.GetMethod<Action<object>>("OnLoaded", viewType);
        var onUnloadedMethod = interfaceInstance.GetMethod<Action<object>>("OnUnloaded", viewType);

        view.Loaded += (sender, args) => onLoadedMethod(sender);
        view.Unloaded += (sender, args) => onUnloadedMethod(sender);
    });

在App.xaml.cs中将默认的ConfigureViewModelLocator替换了。

protected override void ConfigureViewModelLocator()
{
    //base.ConfigureViewModelLocator();
    ViewModelLocationProvider.SetDefaultViewModelFactory(new ViewModelResolver(() => Container)
        .UseDefaultConfigure()
        .ResolveViewModelForView);

    ViewModelLocationProvider.Register<MainWindow, MainWindowViewModel>();

}

第五步:MainViewModel实现IViewLoadedAndUnloadedAware接口,将InitMenu移动到Loaded方法内。

class MainViewModel: BindableBase, IViewLoadedAndUnloadedAware
{
  IContainerExtension _container;
  IRegionManager _regionManager;
  IOperator _operator;
  IDataProvider _dataProvider;

  public MainViewModel(IContainerExtension container, IRegionManager regionManager, IOperator ioperator, IDataProvider dataProvider)
  {
      _container = container;
      _regionManager = regionManager;
      _operator = ioperator;
      _dataProvider = dataProvider;
  }


  public void OnLoaded()
  {
      InitMenu();
  }

  public void OnUnloaded()
  {

  }

万事大吉,运行。报错没有Url,原来是IDataProvider注册的方法有问题,需要用单例来实现,不然登录时候获取的Url就没有保存起来。

containerRegistry.RegisterSingleton<IDataProvider, ApiDataProvider>();

好了,这次真运行起来了。

第六步:我们的Url地址写在了LoginViewModel里了,不够灵活,应该放在配置文件里,可以方便切换登录Url。在AIStudio.Wpf.Client新建App.config.并添加一个类来获取。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>    
    <add key="ServerIP" value="http://121.36.12.76:5000"/>
  </appSettings>  
</configuration>
public class LocalSetting
{
    public static string ServerIP { get; set; } = ConfigurationManager.AppSettings["ServerIP"];
}

用的地方:

private string _serverIP = LocalSetting.ServerIP;
public string ServerIP
{
  get { return _serverIP; }
  set
  {
      SetProperty(ref _serverIP, value);
  }
}

第七步:这个时候点击菜单中的框架介绍,将打不开对应界面了,需要将后台获取到的Code与我们的内部的WpfCode对应上。

1.AMenuItem添加字段WpfCode

public string WpfCode
{
    get
    {
        if (Code == null)
            return null;

        var subcode = Code.Replace("/Index", "IndexView").Replace("/TreeList", "TreeView").Replace("/List", "View").Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
        if (subcode.Length == 1)
            return Code;

        subcode[subcode.Length - 1] = $"Views.{subcode[subcode.Length - 1]}";

        if (!subcode[subcode.Length - 1].EndsWith("View"))
        {
            subcode[subcode.Length - 1] = subcode[subcode.Length - 1] + "View";
        }

        return $"AIStudio.Wpf.{string.Join(".", subcode)}";
    }
}

将IntroduceView的注册改成FullName

public class MainWindowModule : IModule
{
    public void OnInitialized(IContainerProvider containerProvider)
    {
        var regionManager = containerProvider.Resolve<IRegionManager>();
        //regionManager.RegisterViewWithRegion("MainContentRegion", typeof(LoginView));

        regionManager.RegisterViewWithRegion("MainContentRegion", typeof(IntroduceView).FullName);
    }

    public void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterForNavigation(typeof(IntroduceView), typeof(IntroduceView).FullName);
    }
}

另外需要改下页面的传参,使用WpfCode替换Code

<!--顶部菜单-->
<Style x:Key="MenuItemStyle" TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MahApps.Styles.MenuItem}">
    <Setter Property="MenuItem.Command" Value="{Binding Command}"/>
    <Setter Property="MenuItem.CommandParameter" Value="{Binding WpfCode}"/>
</Style>

第八步:目前我们只实现了一个菜单,其它菜单没有实现的时候应该给个提示,是不是友好一些。RequestNavigate添加回调方法NavigationComplete。

_regionManager.RequestNavigate(RegionName, item.WpfCode, NavigationComplete);

private void NavigationComplete(NavigationResult result)
{
    if (result.Result == false)
    {
        WindowBase.ShowMessageQueue($"{result.Context.Uri.ToString()}打开失败", Identifier);
    }
}

好了,结束。 下一章:实现权限管理的菜单吧。

源码地址:https://gitee.com/akwkevin/aistudio.-wpf.-client.-stepby-step

每一章都有自己的Tag,按照链接进去直接下载就是本章内容。

另外推荐一下我的Wpf客户端框架:https://gitee.com/akwkevin/aistudio.-wpf.-aclient

posted @ 2021-08-21 21:17  竹天笑  阅读(821)  评论(0编辑  收藏  举报