前言:接入后台数据,前后台实体类都基本一直,稍微各有各的扩展,后台可以采用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