Kaynağa Gözat

新增刷卡登录功能及刷卡器参数配置支持

新增刷卡登录窗口及视图模型,支持刷卡和账号密码两种登录方式。扩展配置服务,增加刷卡器参数的读写和持久化,系统设置界面支持刷卡器参数配置。数据库服务新增按用户对象设置当前用户及登录日志。主窗口和登录界面调整,登录界面新增刷卡登录按钮。串口协议增加刷卡数据接收事件。完善相关接口和工程文件,提升系统易用性和安全性。
孝锋 徐 8 ay önce
ebeveyn
işleme
e96c7cf778

+ 23 - 1
TeamAAS-VM/App.xaml.cs

@@ -53,7 +53,29 @@ namespace TeamAAS_VP
         protected override Window CreateShell()
         {
             Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
-            return base.Container.Resolve<MainWindowView>();
+            var mainWindow = this.Container.Resolve<MainWindowView>();
+            var _configService = this.Container.Resolve<IConfigService>();
+            var _databaseInitializer = this.Container.Resolve<IDatabaseInitializer>();
+            // 1. 读取配置参数
+            _configService.LoadAll();
+            try
+            {
+                _databaseInitializer.InitializeDatabase();
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("Database initialization failed during application startup", ex);
+            }
+            // 设置语言
+            var currentlanguage = _configService.GetSystemConfiguration().CurrentLanguage;
+            _configService.SetCurrentLanguage(currentlanguage);
+            var cardLogin = this.Container.Resolve<CardLoginWindow>();
+            var result = cardLogin.ShowDialog();
+            if (result is bool st1 && !st1)
+            {
+                this.Shutdown();
+            }
+            return mainWindow;
         }
 
         protected override void RegisterTypes(IContainerRegistry containerRegistry)

+ 5 - 0
TeamAAS-VM/Core/Lights/SerialPortProtocol.cs

@@ -132,7 +132,12 @@ namespace TeamAAS_VP.Core.Lights
                     {
                         CacheTimeout = TimeSpan.FromMilliseconds(100)
                     });
+                _client.Received = async (c, e) =>
+                {
 
+                    string Data = e.ByteBlock.Span.ToString(Encoding); //接收到数据
+                    DataReceived?.Invoke(this, Data.Trim());
+                };
                 _client.Setup(config);
 
                 var result = await _client.TryConnectAsync();

+ 19 - 0
TeamAAS-VM/Data/SystemDatabaseService.cs

@@ -133,6 +133,25 @@ namespace TeamAAS_VP.Data
             await RecordUserLoginAsync(rec);
         }
 
+        /// <summary>
+        /// 将指定用户设为当前用户(会写入一条登录记录,标记为成功)
+        /// </summary>
+        /// <param name="user"></param>
+        /// <returns></returns>
+        public async Task SetCurrentUserAsync(User user)
+        {
+            CurrentUser = user;
+            // 记录一条成功的登录记录
+            var rec = new UserLoginRecord
+            {
+                UserId = user.Id,
+                Success = true,
+                Time = DateTime.UtcNow,
+                Message = user.UserName
+            };
+            await RecordUserLoginAsync(rec);
+        }
+
         /// <summary>
         /// 获取最近一次成功登录的用户(视为当前用户)。
         /// </summary>

+ 25 - 0
TeamAAS-VM/Interfaces/IConfigService.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Threading.Tasks;
+using TeamAAS_VP.Enums;
 using TeamAAS_VP.Models;
 using TeamAAS_VP.Models.Calibration;
 using TeamAAS_VP.Models.Feeder;
@@ -268,6 +269,18 @@ namespace TeamAAS_VP.Interfaces
         /// </summary>
         void SaveSystemConfiguration(SystemConfiguration systemConfiguration);
 
+        /// <summary>
+        /// 获取当前语言
+        /// </summary>
+        /// <returns></returns>
+        Language GetCurrentLanguage();
+
+        /// <summary>
+        /// 设置当前语言
+        /// </summary>
+        /// <param name="language"></param>
+        void SetCurrentLanguage(Language language);
+
         //清料任务
 
         /// <summary>
@@ -477,5 +490,17 @@ namespace TeamAAS_VP.Interfaces
         /// 保存设备信息配置
         /// </summary>
         void SaveDeviceInfo(DeviceInfo deviceInfo);
+
+        //刷卡登录
+        /// <summary>
+        /// 获取刷卡器配置
+        /// </summary>
+        /// <returns></returns>
+        SerialPortConfig GetCardReaderConfig();
+        /// <summary>
+        /// 保存刷卡器配置
+        /// </summary>
+        /// <param name="config"></param>
+        void SaveCardReaderConfig(SerialPortConfig config);
     }
 }

+ 7 - 0
TeamAAS-VM/Interfaces/ISystemDatabaseService.cs

@@ -123,6 +123,13 @@ namespace TeamAAS_VP.Interfaces
         /// </remarks>
         Task SetCurrentUserAsync(int userId);
 
+        /// <summary>
+        /// 将指定用户设为当前用户(会写入一条登录记录,标记为成功)
+        /// </summary>
+        /// <param name="user"></param>
+        /// <returns></returns>
+        Task SetCurrentUserAsync(User user);
+
         /// <summary>
         /// 获取当前(最近一次成功登录的)用户。
         /// </summary>

+ 142 - 3
TeamAAS-VM/Services/ConfigService.cs

@@ -4,10 +4,14 @@ using SqlSugar;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Globalization;
 using System.IO;
 using System.Linq;
+using System.Threading;
 using System.Threading.Tasks;
 using TeamAAS_VP.Core;
+using TeamAAS_VP.Data; // for ISystemDatabaseService (if used)
+using TeamAAS_VP.Enums;
 using TeamAAS_VP.Interfaces;
 using TeamAAS_VP.Models;
 using TeamAAS_VP.Models.Calibration;
@@ -15,8 +19,8 @@ using TeamAAS_VP.Models.Feeder;
 using TeamAAS_VP.Models.Lights;
 using TeamAAS_VP.Models.PLC;
 using TeamAAS_VP.Models.ScrewDriver;
-using TeamAAS_VP.Data; // for ISystemDatabaseService (if used)
-using System.Threading;
+using TeamAAS_VP.Resources.Languages;
+using WPFLocalizeExtension.Engine;
 
 namespace TeamAAS_VP.Services
 {
@@ -46,6 +50,8 @@ namespace TeamAAS_VP.Services
             public static readonly string ScrewDriverConfigurationPath = "..//Config//ScrewDriverConfiguration.cfg";
             // 螺丝供料器(物料)配置路径
             public static readonly string ScrewFeedersConfigurationPath = "..//Config//ScrewFeedersConfiguration.cfg";
+            // 刷卡器配置路径
+            public static readonly string CardReaderConfigurationPath = "..//Config//CardReaderConfiguration.cfg";
         }
 
         private readonly object _sync = new object();
@@ -69,7 +75,8 @@ namespace TeamAAS_VP.Services
         public TeamAAS_VP.Models.ScrewDriver.ScrewDriverConfig ScrewDriverConfig { get { lock(_sync) { return _screwDriverConfig; } } }
 
         private ISystemDatabaseService _systemDatabaseService;
-
+        // 刷卡器配置
+        private SerialPortConfig _cardReaderConfig;
         /// <summary>
         /// 注入系统数据库服务(可选),用于记录螺丝批次变更等记录。
         /// </summary>
@@ -221,6 +228,15 @@ namespace TeamAAS_VP.Services
                     _deviceInfo = new DeviceInfo();
                     FileHelper.WriteJsonFile(_deviceInfo, ConfigPaths.DeviceInfoConfigurationPath);
                 }
+
+                // load card reader configuration
+                if (File.Exists(ConfigPaths.CardReaderConfigurationPath))
+                    _cardReaderConfig = FileHelper.ReadJsonFile<SerialPortConfig>(ConfigPaths.CardReaderConfigurationPath);
+                else
+                {
+                    _cardReaderConfig = new SerialPortConfig();
+                    FileHelper.WriteJsonFile(_cardReaderConfig, ConfigPaths.CardReaderConfigurationPath);
+                }
             }
         }
 
@@ -259,6 +275,9 @@ namespace TeamAAS_VP.Services
                 // save device info
                 if (_deviceInfo != null)
                     FileHelper.WriteJsonFile(_deviceInfo, ConfigPaths.DeviceInfoConfigurationPath);
+                // save card reader config
+                if (_cardReaderConfig != null)
+                    FileHelper.WriteJsonFile(_cardReaderConfig, ConfigPaths.CardReaderConfigurationPath);
             }
         }
 
@@ -761,6 +780,69 @@ namespace TeamAAS_VP.Services
                 FileHelper.WriteJsonFile(systemConfiguration, ConfigPaths.SystemConfigurationPath);
             }
         }
+
+        /// <summary>
+        /// 获取当前语言
+        /// </summary>
+        /// <returns></returns>
+        public Language GetCurrentLanguage()
+        {
+            lock (_sync)
+            {
+                var config = GetSystemConfiguration();
+                return config.CurrentLanguage;
+            }
+        }
+
+        /// <summary>
+        /// 设置当前语言
+        /// </summary>
+        /// <param name="language"></param>
+        public void SetCurrentLanguage(Language language)
+        {
+            lock (_sync)
+            {
+                var config = GetSystemConfiguration();
+                config.CurrentLanguage = language;
+                if (language == Enums.Language.ChineseSimplified)
+                {
+                    var culture = new CultureInfo("zh-CN");
+                    App.Current.Dispatcher.Thread.CurrentCulture = culture;
+                    App.Current.Dispatcher.Thread.CurrentUICulture = culture;
+                    LocalizeDictionary.Instance.Culture = culture;
+                    Lang.Culture = culture;
+                    System.Windows.Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Microsoft YaHei");
+                }
+                else if (language == Enums.Language.ChineseTraditional)
+                {
+                    var culture = new CultureInfo("zh-TW");
+                    App.Current.Dispatcher.Thread.CurrentCulture = culture;
+                    App.Current.Dispatcher.Thread.CurrentUICulture = culture;
+                    LocalizeDictionary.Instance.Culture = culture;
+                    Lang.Culture = culture;
+                    System.Windows.Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Microsoft YaHei");
+                }
+                else if (language == Enums.Language.English)
+                {
+                    var culture = new CultureInfo("en");
+                    App.Current.Dispatcher.Thread.CurrentCulture = culture;
+                    App.Current.Dispatcher.Thread.CurrentUICulture = culture;
+                    LocalizeDictionary.Instance.Culture = culture;
+                    Lang.Culture = culture;
+                    System.Windows.Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Segoe UI");
+                }
+                else if (language == Enums.Language.Japanese)
+                {
+                    var culture = new CultureInfo("ja-JP");
+                    App.Current.Dispatcher.Thread.CurrentCulture = culture;
+                    App.Current.Dispatcher.Thread.CurrentUICulture = culture;
+                    LocalizeDictionary.Instance.Culture = culture;
+                    Lang.Culture = culture;
+                    System.Windows.Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Segoe UI");
+                }
+                SaveSystemConfiguration(config);
+            }
+        }
         #endregion
 
         #region Feeder清料任务
@@ -1290,6 +1372,63 @@ namespace TeamAAS_VP.Services
         }
         #endregion
 
+        #region 用户登录刷卡器
+        /// <summary>
+        /// 获取刷卡器配置
+        /// </summary>
+        /// <returns></returns>
+        public SerialPortConfig GetCardReaderConfig()
+        {
+            lock (_sync)
+            {
+                if (_cardReaderConfig != null)
+                    return _cardReaderConfig;
+
+                if (File.Exists(ConfigPaths.CardReaderConfigurationPath))
+                {
+                    try
+                    {
+                        _cardReaderConfig = FileHelper.ReadJsonFile<SerialPortConfig>(ConfigPaths.CardReaderConfigurationPath);
+                    }
+                    catch
+                    {
+                        // 若读取失败,创建默认配置并覆盖文件(与项目中其他配置策略一致)
+                        _cardReaderConfig = new SerialPortConfig();
+                        try { FileHelper.WriteJsonFile(_cardReaderConfig, ConfigPaths.CardReaderConfigurationPath); } catch { }
+                    }
+                }
+                else
+                {
+                    _cardReaderConfig = new SerialPortConfig();
+                    try { FileHelper.WriteJsonFile(_cardReaderConfig, ConfigPaths.CardReaderConfigurationPath); } catch { }
+                }
+
+                return _cardReaderConfig;
+            }
+        }
+
+        /// <summary>
+        /// 保存刷卡器配置
+        /// </summary>
+        /// <param name="config"></param>
+        public void SaveCardReaderConfig(SerialPortConfig config)
+        {
+            if (config == null) return;
+            lock (_sync)
+            {
+                _cardReaderConfig = config;
+                try
+                {
+                    FileHelper.WriteJsonFile(_cardReaderConfig, ConfigPaths.CardReaderConfigurationPath);
+                }
+                catch
+                {
+                    // 忽略持久化错误(与项目中其它保存方法一致),可在需要时添加日志记录
+                }
+            }
+        }
+        #endregion
+
         /// <summary>
         /// 释放资源并保存当前内存中的所有配置到文件。
         /// </summary>

+ 8 - 0
TeamAAS-VM/TeamAAS-VP.csproj

@@ -542,6 +542,7 @@
     <Compile Include="Services\MesService.cs" />
     <Compile Include="ViewModels\DebugMod\PlcRobotManualStepViewModel.cs" />
     <Compile Include="ViewModels\Product\ImageDisplayViewModel.cs" />
+    <Compile Include="ViewModels\User\CardLoginWindowViewModel.cs" />
     <Compile Include="Views\Product\VisionDynamicAccuracyAnalyzer.xaml.cs">
       <DependentUpon>VisionDynamicAccuracyAnalyzer.xaml</DependentUpon>
     </Compile>
@@ -1070,6 +1071,9 @@
     <Compile Include="Views\UserView.xaml.cs">
       <DependentUpon>UserView.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Views\User\CardLoginWindow.xaml.cs">
+      <DependentUpon>CardLoginWindow.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Views\User\LoginView.xaml.cs">
       <DependentUpon>LoginView.xaml</DependentUpon>
     </Compile>
@@ -1457,6 +1461,10 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="Views\User\CardLoginWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="Views\User\LoginView.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>

+ 2 - 57
TeamAAS-VM/ViewModels/MainWindowViewModel.cs

@@ -150,16 +150,6 @@ namespace TeamAAS_VP.ViewModels
             LoadedCommand = new DelegateCommand(OnLoad);
             ClosedCommand = new DelegateCommand(OnClose);
             Status = management.Status;
-            try
-            {
-                _databaseInitializer.InitializeDatabase();
-            }
-            catch (Exception ex)
-            {
-                LogHelper.WriteLogError("Database initialization failed during application startup", ex);
-            }
-
-            
         }
 
         #region 方法
@@ -220,11 +210,6 @@ namespace TeamAAS_VP.ViewModels
 
         private async void OnLoad()
         {
-            // 1. 读取配置参数
-            _configService.LoadAll();
-            // 2. 加载校准
-            _calibrationService.LoadAll();
-
             // 设置标题和版本号
             var systemconfig = _configService.GetSystemConfiguration();
             MyTitle = systemconfig.Title;
@@ -233,48 +218,6 @@ namespace TeamAAS_VP.ViewModels
             systemconfig.SoftwareVersion = AppVersion;
             _configService.SaveSystemConfiguration(systemconfig);
 
-
-            // 设置语言
-            #region 设置语言
-            var currentlanguage = _configService.GetSystemConfiguration().CurrentLanguage;
-            if (currentlanguage == Enums.Language.ChineseSimplified)
-            {
-                var culture = new CultureInfo("zh-CN");
-                App.Current.Dispatcher.Thread.CurrentCulture = culture;
-                App.Current.Dispatcher.Thread.CurrentUICulture = culture;
-                LocalizeDictionary.Instance.Culture = culture;
-                Lang.Culture = culture;
-                Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Microsoft YaHei");
-            }
-            else if (currentlanguage == Enums.Language.ChineseTraditional)
-            {
-                var culture = new CultureInfo("zh-TW");
-                App.Current.Dispatcher.Thread.CurrentCulture = culture;
-                App.Current.Dispatcher.Thread.CurrentUICulture = culture;
-                LocalizeDictionary.Instance.Culture = culture;
-                Lang.Culture = culture;
-                Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Microsoft YaHei");
-            }
-            else if (currentlanguage == Enums.Language.English)
-            {
-                var culture = new CultureInfo("en");
-                App.Current.Dispatcher.Thread.CurrentCulture = culture;
-                App.Current.Dispatcher.Thread.CurrentUICulture = culture;
-                LocalizeDictionary.Instance.Culture = culture;
-                Lang.Culture = culture;
-                Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Segoe UI");
-            }
-            else if (currentlanguage == Enums.Language.Japanese)
-            {
-                var culture = new CultureInfo("ja-JP");
-                App.Current.Dispatcher.Thread.CurrentCulture = culture;
-                App.Current.Dispatcher.Thread.CurrentUICulture = culture;
-                LocalizeDictionary.Instance.Culture = culture;
-                Lang.Culture = culture;
-                Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Segoe UI");
-            }
-            #endregion
-
             SendTaskMessage(Lang.软件启动, MessageLevel.Info);
             StartTime = DateTime.Now;
             _regionManager.RequestNavigate("MainRegionContext", "HomeView");
@@ -290,6 +233,8 @@ namespace TeamAAS_VP.ViewModels
                 _eventAggregator.GetEvent<ProgressNotification>().Publish(new UProgressBar.ProgressParameter { Maximum = max, Minimum = 0, SubTitle = $"{Lang.初始化中}...", Message = $"{Lang.初始化数据库}...", Value = curvalue });
             }));
 
+            // 2. 加载校准
+            _calibrationService.LoadAll();
             //-------------------SETP 1: 初始化数据库-----------------------------------------------------------------------
             await Task.Delay(200);
             //初始化数据库

+ 17 - 1
TeamAAS-VM/ViewModels/Product/VisionStaticAccuracyAnalyzerViewModel.cs

@@ -967,8 +967,24 @@ namespace TeamAAS_VP.ViewModels.Product
                                             //如果需要转换为绝对坐标,则再创建绝对X、绝对Y两列
                                             if (needConvertToAbsolute)
                                             {
+                                                double[] point = new double[3];
                                                 var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
-                                                (bool IsSucceed, double X, double Y, double U) = _calibrationService.ConvertPixelToPosition((double.Parse(subitems[0]), double.Parse(subitems[1]), double.Parse(subitems[2])), null, calibration);
+
+                                                var Robot = _robotService.GetRobot(SelectProcedure.RobotId);
+                                                if (Robot != null)
+                                                {
+                                                    //获取机器人当前坐标系
+                                                    RPoint rPoint = Robot.GetRobotPos();
+
+                                                    point[0] = rPoint.X;
+                                                    point[1] = rPoint.Y;
+                                                    point[2] = rPoint.U;
+                                                    if (calibration.CameraMount == CameraMount.FixedUp || calibration.CameraMount == CameraMount.FixedDown)
+                                                    {
+                                                        point = null;
+                                                    }
+                                                }
+                                                (bool IsSucceed, double X, double Y, double U) = _calibrationService.ConvertPixelToPosition((double.Parse(subitems[0]), double.Parse(subitems[1]), double.Parse(subitems[2])), point, calibration);
                                                 result.Add(Math.Round(X, 5));
                                                 result.Add(Math.Round(Y, 5));
                                             }

+ 34 - 36
TeamAAS-VM/ViewModels/SettingViewModel.cs

@@ -333,6 +333,12 @@ namespace TeamAAS_VP.ViewModels
             set { SetProperty(ref _DeviceInfo, value); }
         }
 
+        private SerialPortConfig _CardReaderConfig;
+        public SerialPortConfig CardReaderConfig
+        {
+            get { return _CardReaderConfig; }
+            set { SetProperty(ref _CardReaderConfig, value); }
+        }
         #endregion
 
         #region 命令
@@ -428,6 +434,9 @@ namespace TeamAAS_VP.ViewModels
         public DelegateCommand<object> LightModelChangedCommand =>
             _LightModelChangedCommand ?? (_LightModelChangedCommand = new DelegateCommand<object>(ExecuteLightModelChangedCommand));
 
+        private DelegateCommand _SaveCardReaderConfigCommand;
+        public DelegateCommand SaveCardReaderConfigCommand =>
+            _SaveCardReaderConfigCommand ?? (_SaveCardReaderConfigCommand = new DelegateCommand(ExecuteSaveCardReaderConfigCommand));
 
         #endregion
 
@@ -1146,57 +1155,25 @@ namespace TeamAAS_VP.ViewModels
             if (string.Equals(language, "ChineseSimplified"))
             {
                 CurrentLanguage = Enums.Language.ChineseSimplified;
-                var sysConfig = _configService.GetSystemConfiguration();
-                sysConfig.CurrentLanguage = CurrentLanguage;
-                _configService.SaveSystemConfiguration(sysConfig);
-                var culture = new CultureInfo("zh-CN");
-                App.Current.Dispatcher.Thread.CurrentCulture = culture;
-                App.Current.Dispatcher.Thread.CurrentUICulture = culture;
-                LocalizeDictionary.Instance.Culture = culture;
-                Lang.Culture = culture;
-                Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Microsoft YaHei");
+                _configService.SetCurrentLanguage(CurrentLanguage);
                 _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 0.5 });
             }
             else if (string.Equals(language, "ChineseTraditional"))
             {
                 CurrentLanguage = Enums.Language.ChineseTraditional;
-                var sysConfig = _configService.GetSystemConfiguration();
-                sysConfig.CurrentLanguage = CurrentLanguage;
-                _configService.SaveSystemConfiguration(sysConfig);
-                var culture = new CultureInfo("zh-TW");
-                App.Current.Dispatcher.Thread.CurrentCulture = culture;
-                App.Current.Dispatcher.Thread.CurrentUICulture = culture;
-                LocalizeDictionary.Instance.Culture = culture;
-                Lang.Culture = culture;
-                Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Microsoft YaHei");
+                _configService.SetCurrentLanguage(CurrentLanguage);
                 _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 0.5 });
             }
             else if (string.Equals(language, "English"))
             {
                 CurrentLanguage = Enums.Language.English;
-                var sysConfig = _configService.GetSystemConfiguration();
-                sysConfig.CurrentLanguage = CurrentLanguage;
-                _configService.SaveSystemConfiguration(sysConfig);
-                var culture = new CultureInfo("en");
-                App.Current.Dispatcher.Thread.CurrentCulture = culture;
-                App.Current.Dispatcher.Thread.CurrentUICulture = culture;
-                LocalizeDictionary.Instance.Culture = culture;
-                Lang.Culture = culture;
-                Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Segoe UI");
+                _configService.SetCurrentLanguage(CurrentLanguage);
                 _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 0.5 });
             }
             else if (string.Equals(language, "Japanese"))
             {
                 CurrentLanguage = Enums.Language.Japanese;
-                var sysConfig = _configService.GetSystemConfiguration();
-                sysConfig.CurrentLanguage = CurrentLanguage;
-                _configService.SaveSystemConfiguration(sysConfig);
-                var culture = new CultureInfo("ja-JP");
-                App.Current.Dispatcher.Thread.CurrentCulture = culture;
-                App.Current.Dispatcher.Thread.CurrentUICulture = culture;
-                LocalizeDictionary.Instance.Culture = culture;
-                Lang.Culture = culture;
-                Application.Current.Resources["DefaultFont"] = new System.Windows.Media.FontFamily("Segoe UI");
+                _configService.SetCurrentLanguage(CurrentLanguage);
                 _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 0.5 });
             }
         }
@@ -1347,6 +1324,12 @@ namespace TeamAAS_VP.ViewModels
                 DeviceInfo = _configService.GetDeviceInfo();
             }
 
+            if (CardReaderConfig == null)
+            {
+                // load card reader config
+                CardReaderConfig = _configService.GetCardReaderConfig();
+            }
+
             CurrentLanguage = _configService.GetSystemConfiguration().CurrentLanguage;
 
             // load system parameters into UI
@@ -1558,6 +1541,21 @@ namespace TeamAAS_VP.ViewModels
                 }
             }
         }
+
+        /// <summary>
+        /// 保存读卡器配置
+        /// </summary>
+        void ExecuteSaveCardReaderConfigCommand()
+        {
+            try
+            {
+                _configService.SaveCardReaderConfig(CardReaderConfig);
+            }
+            catch (Exception ex)
+            {
+                _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
+            }
+        }
         #endregion
 
         #region 设备信息

+ 287 - 0
TeamAAS-VM/ViewModels/User/CardLoginWindowViewModel.cs

@@ -0,0 +1,287 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using OpenCvSharp;
+using Prism.Commands;
+using Prism.Events;
+using Prism.Ioc;
+using Prism.Mvvm;
+using Prism.Regions;
+using Prism.Services.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using TeamAAS_VP.Core;
+using TeamAAS_VP.Core.Lights;
+using TeamAAS_VP.Data;
+using TeamAAS_VP.Enums;
+using TeamAAS_VP.Events;
+using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Models;
+using TeamAAS_VP.Resources.Languages;
+using TeamAAS_VP.Views;
+using TeamAAS_VP.Views.User;
+
+namespace TeamAAS_VP.ViewModels.User
+{
+    public class CardLoginWindowViewModel : BindableBase
+    {
+        #region 字段
+        IConfigService _configService;
+        private readonly ISystemDatabaseService _systemDatabaseService;
+        private readonly IEventAggregator _eventAggregator;
+        private List<TeamAAS_VP.Models.User> Users;
+        #endregion
+
+        #region 属性
+
+        private ObservableCollection<string> _UserNameList = new ObservableCollection<string>();
+
+        /// <summary>
+        /// 用户列表
+        /// </summary>
+        public ObservableCollection<string> UserNameList
+        {
+            get { return _UserNameList; }
+            set { _UserNameList = value; }
+        }
+
+        private string _SelectedUserName;
+        public string SelectedUserName
+        {
+            get { return _SelectedUserName; }
+            set { SetProperty(ref _SelectedUserName, value); }
+        }
+
+        private string _Password;
+        public string Password
+        {
+            get { return _Password; }
+            set { SetProperty(ref _Password, value); }
+        }
+
+        public bool IsRemember { get; set; }
+
+        public bool IsLocal { get; private set; }
+        public CardLoginWindow View { get; set; }
+
+        //正在登录中
+        private bool _IsLogining;
+        public bool IsLogining
+        {
+            get { return _IsLogining; }
+            set { SetProperty(ref _IsLogining, value); }
+        }
+
+        private int _PageIndex;
+        public int PageIndex
+        {
+            get { return _PageIndex; }
+            set { SetProperty(ref _PageIndex, value); }
+        }
+
+        private Visibility _CardBtnVisibility = Visibility.Visible;
+        public Visibility CardBtnVisibility
+        {
+            get { return _CardBtnVisibility; }
+            set { SetProperty(ref _CardBtnVisibility, value); }
+        }
+
+        public SerialPortProtocol serialPortProtocol { get; set; }
+        #endregion
+
+        #region 命令
+        private DelegateCommand _LoginCommand;
+        public DelegateCommand LoginCommand =>
+            _LoginCommand ?? (_LoginCommand = new DelegateCommand(ExecuteLoginCommand, CanExecuteLoginCommand).ObservesProperty(() => SelectedUserName).ObservesProperty(() => Password).ObservesProperty(() => IsLogining));
+
+        private DelegateCommand _CloseCommand;
+        public DelegateCommand CloseCommand =>
+            _CloseCommand ?? (_CloseCommand = new DelegateCommand(ExecuteCloseCommand));
+
+        private DelegateCommand<string> _TransitionerCommand;
+        public DelegateCommand<string> TransitionerCommand =>
+            _TransitionerCommand ?? (_TransitionerCommand = new DelegateCommand<string>(ExecuteTransitionerCommand));
+
+        private DelegateCommand _LoadedCommand;
+        public DelegateCommand LoadedCommand =>
+            _LoadedCommand ?? (_LoadedCommand = new DelegateCommand(ExecuteLoadedCommand));
+        #endregion
+
+        #region 事件
+
+        #endregion
+
+        public CardLoginWindowViewModel(IConfigService configService, ISystemDatabaseService systemDatabaseService, IEventAggregator eventAggregator)
+        {
+            _configService = configService;
+            _systemDatabaseService = systemDatabaseService;
+            _eventAggregator = eventAggregator;
+        }
+
+
+        #region 方法
+        /// <summary>
+        /// 加载
+        /// </summary>
+        async void ExecuteLoadedCommand()
+        {
+            //获取刷卡器配置
+            var cardConfig = _configService.GetCardReaderConfig();
+            try
+            {
+                Users = _systemDatabaseService.GetAllUsersAsync().GetAwaiter().GetResult()?.ToList();
+            }
+            catch
+            {
+                Users = new List<TeamAAS_VP.Models.User>();
+            }
+            TeamAAS_VP.Models.User rememberUser = null;
+            if (Users != null)
+            {
+                UserNameList.Clear();
+                foreach (var user in Users)
+                {
+                    UserNameList.Add(user.UserName);
+                    if (user.IsRemember)
+                    {
+                        rememberUser = user;
+                    }
+                }
+            }
+
+            //对记住的用户界面绑定
+            if (rememberUser != null)
+            {
+                SelectedUserName = UserNameList.FirstOrDefault(p => p == rememberUser.UserName);
+                Password = rememberUser.UserPassword;
+                IsRemember = true;
+            }
+
+            //创建刷卡器通讯
+            serialPortProtocol = new SerialPortProtocol(cardConfig);
+            serialPortProtocol.DataReceived += CardSerial_DataReceived;
+            bool isConnected = await serialPortProtocol.ConnectAsync();
+            if (!isConnected)
+            {
+                MessageBox.Show("刷卡器连接失败,请检查连接!");
+            }
+        }
+
+        private async void CardSerial_DataReceived(object sender, string code)
+        {
+            if (PageIndex == 0)
+            {
+                if (string.IsNullOrEmpty(code))
+                {
+                    return;
+                }
+                Models.User user = new Models.User();
+                user.UserName = code.Trim().TrimStart('\0');
+                user.userPart = UserPart.Engineer;
+                user.UserPassword = code.Trim().TrimStart('\0'); //刷卡密码默认为卡号
+                user.CreateTime = DateTime.Now;
+                user.Id = -1;
+
+                await _systemDatabaseService.SetCurrentUserAsync(user);
+
+                App.Current.Dispatcher.Invoke(() =>
+                {
+                    _eventAggregator.GetEvent<UserLoginNotification>().Publish(user);
+                    View.DialogResult = true;
+                });
+
+            }
+        }
+
+        public async void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+        {
+            if (serialPortProtocol!=null)
+            {
+                serialPortProtocol.DataReceived -= CardSerial_DataReceived;
+            }
+            await serialPortProtocol?.DisconnectAsync();
+            
+            serialPortProtocol?.Dispose();
+            serialPortProtocol = null;
+        }
+
+        async void ExecuteLoginCommand()
+        {
+            if (Users == null || string.IsNullOrEmpty(SelectedUserName))
+                return;
+
+            IsLogining = true;
+            try
+            {
+                if (_systemDatabaseService.GetCurrentUser() != null)
+                {
+                    if (_systemDatabaseService.GetCurrentUser().UserName == SelectedUserName)
+                    {
+                        View.DialogResult = true;
+                        return;
+                    }
+                }
+
+                // authenticate via system database service
+                var user = await _systemDatabaseService.AuthenticateUserAsync(SelectedUserName, Password);
+                if (user != null)
+                {
+                    // set remember flag via system database service
+                    try
+                    {
+                        if (IsRemember)
+                            await _systemDatabaseService.SetRememberUserAsync(SelectedUserName);
+                        else
+                            await _systemDatabaseService.ClearRememberUserAsync();
+                    }
+                    catch
+                    {
+
+                    }
+
+                    // mark current user in system DB and record login
+                    await _systemDatabaseService.SetCurrentUserAsync(user.Id);
+                    await _systemDatabaseService.RecordUserLoginAsync(new UserLoginRecord { UserId = user.Id, Success = true, Time = DateTime.UtcNow, Message = "Login success" });
+                    _eventAggregator.GetEvent<UserLoginNotification>().Publish(user);
+                    View.DialogResult = true;
+                }
+
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(ex.Message);
+            }
+            finally
+            {
+                IsLogining = false;
+            }
+        }
+
+        bool CanExecuteLoginCommand()
+        {
+            return !string.IsNullOrEmpty(SelectedUserName) && !IsLogining;
+        }
+
+        void ExecuteCloseCommand()
+        {
+            View.DialogResult = false;
+        }
+
+        void ExecuteTransitionerCommand(string parameter)
+        {
+            PageIndex = int.Parse(parameter);
+        }
+
+        public void SetModel(bool isAdmin)
+        {
+            PageIndex = isAdmin ? 1 : 0;
+            CardBtnVisibility = isAdmin ? Visibility.Collapsed : Visibility.Visible;
+        }
+        #endregion
+    }
+}

+ 32 - 14
TeamAAS-VM/ViewModels/User/LoginViewModel.cs

@@ -11,12 +11,13 @@ using System.Collections.ObjectModel;
 using System.Data.Entity;
 using System.Linq;
 using System.Threading.Tasks;
+using TeamAAS_VP.Core;
 using TeamAAS_VP.Data;
 using TeamAAS_VP.Events;
-using TeamAAS_VP.Core;
+using TeamAAS_VP.Interfaces;
 using TeamAAS_VP.Models;
 using TeamAAS_VP.Resources.Languages;
-using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Views.User;
 
 namespace TeamAAS_VP.ViewModels.User
 {
@@ -25,8 +26,7 @@ namespace TeamAAS_VP.ViewModels.User
         private readonly IRegionManager _regionManager;
         private readonly IEventAggregator _eventAggregator;
         private readonly IContainerProvider _container;
-        private readonly ISystemDatabaseService _systemDb;
-		ISystemDatabaseService _systemDatabaseService;
+        private readonly ISystemDatabaseService _systemDatabaseService;
 
 		private List<TeamAAS_VP.Models.User> Users;
         #region 属性
@@ -59,18 +59,23 @@ namespace TeamAAS_VP.ViewModels.User
         /// </summary>
         public DelegateCommand LoginCommand { get; set; }
         public DelegateCommand LoadedCommand { get; set; }
+
+        private DelegateCommand _CardLoginCommand;
+        public DelegateCommand CardLoginCommand =>
+            _CardLoginCommand ?? (_CardLoginCommand = new DelegateCommand(ExecuteCardLoginCommand));
+
+        
         #endregion
 
         #region 事件
 
         #endregion
 
-        public LoginViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, ISystemDatabaseService systemDb,ISystemDatabaseService systemDatabaseService)
+        public LoginViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, ISystemDatabaseService systemDatabaseService)
         {
             _regionManager = regionManager;
             _eventAggregator = ea;
             _container = container;
-            _systemDb = systemDb;
             _systemDatabaseService = systemDatabaseService;
 
 			LoadedCommand = new DelegateCommand(OnLoad);
@@ -82,7 +87,7 @@ namespace TeamAAS_VP.ViewModels.User
             // load users from system database (sync-block safe call)
             try
             {
-                Users = _systemDb.GetAllUsersAsync().GetAwaiter().GetResult()?.ToList();
+                Users = _systemDatabaseService.GetAllUsersAsync().GetAwaiter().GetResult()?.ToList();
             }
             catch
             {
@@ -128,16 +133,16 @@ namespace TeamAAS_VP.ViewModels.User
             try
             {
                 // authenticate via system database service
-                var user = await _systemDb.AuthenticateUserAsync(SelectedUserName, UserPassword);
+                var user = await _systemDatabaseService.AuthenticateUserAsync(SelectedUserName, UserPassword);
                 if (user != null)
                 {
                     // set remember flag via system database service
                     try
                     {
                         if (IsRemember)
-                            await _systemDb.SetRememberUserAsync(SelectedUserName);
+                            await _systemDatabaseService.SetRememberUserAsync(SelectedUserName);
                         else
-                            await _systemDb.ClearRememberUserAsync();
+                            await _systemDatabaseService.ClearRememberUserAsync();
                     }
                     catch
                     {
@@ -145,8 +150,8 @@ namespace TeamAAS_VP.ViewModels.User
                     }
 
                     // mark current user in system DB and record login
-                    await _systemDb.SetCurrentUserAsync(user.Id);
-                    await _systemDb.RecordUserLoginAsync(new UserLoginRecord { UserId = user.Id, Success = true, Time = DateTime.UtcNow, Message = "Login success" });
+                    await _systemDatabaseService.SetCurrentUserAsync(user.Id);
+                    //await _systemDatabaseService.RecordUserLoginAsync(new UserLoginRecord { UserId = user.Id, Success = true, Time = DateTime.UtcNow, Message = "Login success" });
 
                     _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"{Lang.登录成功}!", Duration = 1 });
                     _eventAggregator.GetEvent<UserLoginNotification>().Publish(user);
@@ -155,7 +160,7 @@ namespace TeamAAS_VP.ViewModels.User
             catch (Exception ex)
             {
                 // authentication failed or error
-                await _systemDb.RecordUserLoginAsync(new UserLoginRecord { UserId = 0, Success = false, Time = DateTime.UtcNow, Message = $"Login failed for {SelectedUserName}: {ex.Message}" });
+                await _systemDatabaseService.RecordUserLoginAsync(new UserLoginRecord { UserId = 0, Success = false, Time = DateTime.UtcNow, Message = $"Login failed for {SelectedUserName}: {ex.Message}" });
                 _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.密码错误, Duration = 1 });
             }
         }
@@ -186,7 +191,7 @@ namespace TeamAAS_VP.ViewModels.User
             //获取所有用户
             try
             {
-                Users = _systemDb.GetAllUsersAsync().GetAwaiter().GetResult()?.ToList();
+                Users = _systemDatabaseService.GetAllUsersAsync().GetAwaiter().GetResult()?.ToList();
             }
             catch
             {
@@ -213,6 +218,19 @@ namespace TeamAAS_VP.ViewModels.User
                 IsRemember = true;
             }
         }
+
+        /// <summary>
+        /// 刷卡登录命令
+        /// </summary>
+        void ExecuteCardLoginCommand()
+        {
+            var cardLogin = _container.Resolve<CardLoginWindow>();
+            var result = cardLogin.ShowDialog();
+            if (result is bool st1 && !st1)
+            {
+                
+            }
+        }
         #endregion
     }
 }

+ 78 - 0
TeamAAS-VM/Views/SettingView.xaml

@@ -2368,6 +2368,84 @@
                             </Button>
                         </StackPanel>
                     </Grid>
+                    <!--刷卡参数配置-->
+                    <GroupBox Grid.Row="5"
+                              Grid.ColumnSpan="2"
+                              Header="刷卡器配置"
+                              HorizontalAlignment="Left"
+                              MinWidth="400"
+                              Margin="0,12,0,0">
+                        <Grid Margin="10">
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="auto" />
+                                <ColumnDefinition Width="*" />
+                            </Grid.ColumnDefinitions>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto" />
+                                <RowDefinition Height="Auto" />
+                                <RowDefinition Height="Auto" />
+                                <RowDefinition Height="Auto" />
+                                <RowDefinition Height="Auto" />
+                                <RowDefinition Height="Auto" />
+                            </Grid.RowDefinitions>
+                            <TextBlock Text="PortName:"
+                                       Grid.Row="0"
+                                       Grid.Column="0"
+                                       HorizontalAlignment="Right" />
+                            <TextBox Text="{Binding CardReaderConfig.PortName,Mode=TwoWay}"
+                                     Grid.Row="0"
+                                     Grid.Column="1"
+                                     Margin="6,0" />
+                            <TextBlock Text="BaudRate:"
+                                       Grid.Row="1"
+                                       Grid.Column="0"
+                                       HorizontalAlignment="Right" />
+                            <TextBox Text="{Binding CardReaderConfig.BaudRate,Mode=TwoWay}"
+                                     Grid.Row="1"
+                                     Grid.Column="1"
+                                     Margin="6,0" />
+                            <TextBlock Text="Parity:"
+                                       Grid.Row="2"
+                                       Grid.Column="0"
+                                       HorizontalAlignment="Right" />
+                            <ComboBox Grid.Row="2"
+                                      Grid.Column="1"
+                                      SelectedItem="{Binding CardReaderConfig.Parity,Mode=TwoWay}"
+                                      ItemsSource="{Binding Source={StaticResource Parity}}" />
+                            <TextBlock Text="StopBits:"
+                                       Grid.Row="3"
+                                       Grid.Column="0"
+                                       HorizontalAlignment="Right" />
+                            <ComboBox Grid.Row="3"
+                                      Grid.Column="1"
+                                      SelectedItem="{Binding CardReaderConfig.StopBits,Mode=TwoWay}"
+                                      ItemsSource="{Binding Source={StaticResource StopBits}}" />
+                            <TextBlock Text="DataBits:"
+                                       Grid.Row="4"
+                                       Grid.Column="0"
+                                       HorizontalAlignment="Right" />
+                            <TextBox Text="{Binding CardReaderConfig.DataBits,Mode=TwoWay}"
+                                     Grid.Row="4"
+                                     Grid.Column="1"
+                                     Margin="6,0" />
+
+                            <Button Grid.Row="5"
+                                    Grid.Column="1"
+                                    Margin="0,6"
+                                    Style="{StaticResource MaterialDesignRaisedButton}"
+                                    materialDesign:ButtonAssist.CornerRadius="10"
+                                    MinWidth="120"
+                                    Command="{Binding SaveCardReaderConfigCommand}">
+                                <StackPanel Orientation="Horizontal"
+                                            HorizontalAlignment="Center">
+                                    <materialDesign:PackIcon Kind="ContentSaveCheck"
+                                                             Margin="0,0,6,0" />
+                                    <TextBlock VerticalAlignment="Center"
+                                               Text="保存刷卡器配置" />
+                                </StackPanel>
+                            </Button>
+                        </Grid>
+                    </GroupBox>
                 </StackPanel>
             </TabItem>
 

+ 373 - 0
TeamAAS-VM/Views/User/CardLoginWindow.xaml

@@ -0,0 +1,373 @@
+<Window x:Class="TeamAAS_VP.Views.User.CardLoginWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
+        xmlns:prism="http://prismlibrary.com/"
+        xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
+        xmlns:vm="clr-namespace:TeamAAS_VP.ViewModels.User"
+        xmlns:local="clr-namespace:TeamAAS_VP"
+        xmlns:converter="clr-namespace:TeamAAS_VP.ValueConverter"
+        xmlns:lex="http://wpflocalizeextension.codeplex.com"
+        lex:LocalizeDictionary.DesignCulture="zh-CN"
+        lex:ResxLocalizationProvider.DefaultAssembly="TeamAAS-VP"
+        lex:ResxLocalizationProvider.DefaultDictionary="Lang"
+        mc:Ignorable="d"
+        d:DataContext="{d:DesignInstance Type=vm:CardLoginWindowViewModel}"
+        prism:ViewModelLocator.AutoWireViewModel="True"
+        Background="Transparent"
+        WindowState="Normal"
+        WindowStyle="None"
+        WindowStartupLocation="CenterScreen"
+        AllowsTransparency="True"
+        Width="563"
+        Height="561"
+        FontFamily="{DynamicResource DefaultFont}">
+    <b:Interaction.Triggers>
+        <b:EventTrigger EventName="Loaded">
+            <b:InvokeCommandAction Command="{Binding LoadedCommand}" />
+        </b:EventTrigger>
+        <b:EventTrigger EventName="Closing">
+            <b:CallMethodAction MethodName="Window_Closing"
+                                TargetObject="{Binding}" />
+        </b:EventTrigger>
+    </b:Interaction.Triggers>
+    <Border Width="563"
+            Height="561"
+            CornerRadius="60"
+            BorderBrush="Black"
+            BorderThickness="1">
+        <Border.Background>
+            <LinearGradientBrush EndPoint="0.497,1.007"
+                                 StartPoint="0.494,0.009">
+                <GradientStop Color="#FF309EAF" />
+                <GradientStop Color="White"
+                              Offset="1" />
+            </LinearGradientBrush>
+        </Border.Background>
+        <Grid>
+            <Button Content="X"
+                    FontSize="16"
+                    FontWeight="Bold"
+                    Width="32"
+                    Height="32"
+                    Padding="0"
+                    HorizontalAlignment="Right"
+                    VerticalAlignment="Top"
+                    Margin="0,20,18,0"
+                    Command="{Binding CloseCommand}"
+                    Background="Transparent"
+                    BorderBrush="Transparent"
+                    Foreground="Black" />
+            <TextBlock Text="用户登录"
+                       HorizontalAlignment="Center"
+                       VerticalAlignment="Top"
+                       FontSize="32"
+                       Margin="0,58,0,0"
+                       FontWeight="Bold" />
+            <materialDesign:Transitioner Margin="20,110,20,20"
+                                         AutoApplyTransitionOrigins="True"
+                                         d:SelectedIndex="1"
+                                         SelectedIndex="{Binding PageIndex}">
+                <materialDesign:TransitionerSlide OpeningEffect="{materialDesign:TransitionEffect FadeIn}">
+                    <Grid Background="Transparent">
+                        <Grid>
+                            <!-- 刷卡动画区域 -->
+                            <Border BorderThickness="1"
+                                    BorderBrush="#bdc3c7"
+                                    Background="#f8f9fa"
+                                    CornerRadius="8"
+                                    Margin="0,10"
+                                    VerticalAlignment="Top"
+                                    HorizontalAlignment="Center"
+                                    Padding="30">
+                                <Canvas Width="300"
+                                        Height="200">
+                                    <!-- 刷卡器主体 -->
+                                    <Rectangle Canvas.Left="50"
+                                               Canvas.Top="70"
+                                               Width="200"
+                                               Height="60"
+                                               Fill="#34495e"
+                                               RadiusX="5"
+                                               RadiusY="5">
+                                        <Rectangle.Effect>
+                                            <DropShadowEffect ShadowDepth="2"
+                                                              Direction="270"
+                                                              BlurRadius="5"
+                                                              Opacity="0.2" />
+                                        </Rectangle.Effect>
+                                    </Rectangle>
+
+                                    <!-- 刷卡槽 -->
+                                    <Rectangle Canvas.Left="60"
+                                               Canvas.Top="75"
+                                               Width="180"
+                                               Height="10"
+                                               Fill="#7f8c8d"
+                                               RadiusX="2"
+                                               RadiusY="2" />
+
+                                    <!-- 动态卡片 -->
+                                    <Border x:Name="AnimatedCard"
+                                            Canvas.Left="50"
+                                            Canvas.Top="10"
+                                            Width="180"
+                                            Height="100"
+                                            Background="#3498db"
+                                            CornerRadius="8"
+                                            BorderThickness="1"
+                                            BorderBrush="#2980b9">
+                                        <Border.Effect>
+                                            <DropShadowEffect ShadowDepth="3"
+                                                              Direction="270"
+                                                              BlurRadius="10"
+                                                              Opacity="0.3" />
+                                        </Border.Effect>
+
+                                        <!-- 卡片动画 -->
+                                        <Border.Triggers>
+                                            <EventTrigger RoutedEvent="Loaded">
+                                                <BeginStoryboard>
+                                                    <Storyboard RepeatBehavior="Forever">
+                                                        <!-- 卡片下落动画 -->
+                                                        <DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)"
+                                                                         From="10"
+                                                                         To="70"
+                                                                         Duration="0:0:1"
+                                                                         AutoReverse="True"
+                                                                         BeginTime="0:0:0" />
+
+                                                        <!-- 卡片透明度变化 -->
+                                                        <DoubleAnimation Storyboard.TargetProperty="Opacity"
+                                                                         From="1"
+                                                                         To="0.7"
+                                                                         Duration="0:0:0.5"
+                                                                         AutoReverse="True"
+                                                                         BeginTime="0:0:0.5" />
+                                                    </Storyboard>
+                                                </BeginStoryboard>
+                                            </EventTrigger>
+                                        </Border.Triggers>
+
+                                        <!-- 卡片内部设计 -->
+                                        <Grid>
+                                            <Grid.RowDefinitions>
+                                                <RowDefinition Height="*" />
+                                                <RowDefinition Height="Auto" />
+                                            </Grid.RowDefinitions>
+
+                                            <TextBlock Text="员工卡"
+                                                       Foreground="White"
+                                                       FontSize="14"
+                                                       FontWeight="Bold"
+                                                       HorizontalAlignment="Center"
+                                                       VerticalAlignment="Center" />
+
+                                            <Rectangle Grid.Row="1"
+                                                       Height="20"
+                                                       Fill="#2980b9"
+                                                       RadiusX="0"
+                                                       RadiusY="0,0,8,8" />
+                                            <TextBlock Grid.Row="1"
+                                                       Text="●●●● 1234"
+                                                       Foreground="White"
+                                                       FontSize="10"
+                                                       HorizontalAlignment="Center"
+                                                       VerticalAlignment="Center" />
+                                        </Grid>
+                                    </Border>
+
+                                    <!-- 扫描光线效果 -->
+                                    <Rectangle x:Name="ScanBeam"
+                                               Canvas.Left="60"
+                                               Canvas.Top="75"
+                                               Width="180"
+                                               Height="2"
+                                               Fill="#2ecc71"
+                                               Opacity="0">
+                                        <Rectangle.Effect>
+                                            <BlurEffect Radius="3" />
+                                        </Rectangle.Effect>
+
+                                        <Rectangle.Triggers>
+                                            <EventTrigger RoutedEvent="Loaded">
+                                                <BeginStoryboard>
+                                                    <Storyboard RepeatBehavior="Forever">
+                                                        <!-- 扫描光束移动动画 -->
+                                                        <DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)"
+                                                                         From="75"
+                                                                         To="125"
+                                                                         Duration="0:0:0.8"
+                                                                         BeginTime="0:0:0.5" />
+
+                                                        <!-- 扫描光束透明度动画 -->
+                                                        <DoubleAnimation Storyboard.TargetProperty="Opacity"
+                                                                         From="0"
+                                                                         To="1"
+                                                                         Duration="0:0:0.2"
+                                                                         AutoReverse="True"
+                                                                         BeginTime="0:0:0.5" />
+                                                    </Storyboard>
+                                                </BeginStoryboard>
+                                            </EventTrigger>
+                                        </Rectangle.Triggers>
+                                    </Rectangle>
+
+                                    <!-- 状态指示灯 -->
+                                    <Ellipse Canvas.Left="20"
+                                             Canvas.Top="80"
+                                             Width="12"
+                                             Height="12"
+                                             Fill="#e74c3c">
+                                        <Ellipse.Triggers>
+                                            <EventTrigger RoutedEvent="Loaded">
+                                                <BeginStoryboard>
+                                                    <Storyboard RepeatBehavior="Forever">
+                                                        <!-- 指示灯颜色变化动画 -->
+                                                        <ColorAnimation Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)"
+                                                                        From="#e74c3c"
+                                                                        To="#2ecc71"
+                                                                        Duration="0:0:1"
+                                                                        AutoReverse="True"
+                                                                        BeginTime="0:0:0.5" />
+                                                    </Storyboard>
+                                                </BeginStoryboard>
+                                            </EventTrigger>
+                                        </Ellipse.Triggers>
+                                    </Ellipse>
+                                </Canvas>
+                            </Border>
+
+                            <!-- 提示文字 -->
+                            <TextBlock Text="请刷卡..."
+                                       HorizontalAlignment="Center"
+                                       VerticalAlignment="Bottom"
+                                       Margin="0,120"
+                                       FontSize="16"
+                                       Foreground="#2c3e50"
+                                       FontWeight="SemiBold" />
+                        </Grid>
+                        <Button x:Name="NameLoginButton"
+                                Height="50"
+                                Width="50"
+                                materialDesign:ButtonAssist.CornerRadius="5"
+                                HorizontalAlignment="Center"
+                                VerticalAlignment="Bottom"
+                                Margin="0,0,0,44"
+                                ToolTip="账户登录"
+                                Command="{Binding TransitionerCommand}"
+                                CommandParameter="1"
+                                Padding="0"
+                                IsEnabled="{Binding IsLogining,Converter={StaticResource InvertBooleanConverter}}">
+                            <Button.Content>
+                                <materialDesign:PackIcon Kind="User"
+                                                         Height="32"
+                                                         Width="32" />
+                            </Button.Content>
+                        </Button>
+                    </Grid>
+                </materialDesign:TransitionerSlide>
+                <Grid HorizontalAlignment="Center"
+                      Margin="0,50">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="Auto" />
+
+                        <RowDefinition Height="Auto" />
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto" />
+                        <ColumnDefinition Width="Auto" />
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Grid.Row="0"
+                               Grid.Column="0"
+                               HorizontalAlignment="Left"
+                               VerticalAlignment="Center"
+                               Margin="20"
+                               FontSize="18"
+                               FontWeight="Bold"
+                               Text="用户名:" />
+                    <ComboBox Grid.Row="0"
+                              Grid.Column="1"
+                              HorizontalAlignment="Left"
+                              VerticalAlignment="Center"
+                              Margin="20"
+                              Width="200"
+                              FontSize="16"
+                              materialDesign:HintAssist.Hint="{lex:Loc 用户}"
+                              HorizontalContentAlignment="Left"
+                              Style="{StaticResource MaterialDesignFloatingHintComboBox}"
+                              ItemsSource="{Binding UserNameList,Mode=TwoWay}"
+                              SelectedItem="{Binding SelectedUserName,Mode=TwoWay}">
+                    </ComboBox>
+                    <TextBlock Grid.Row="1"
+                               Grid.Column="0"
+                               HorizontalAlignment="Left"
+                               VerticalAlignment="Center"
+                               Margin="20"
+                               FontSize="18"
+                               FontWeight="Bold"
+                               Text="密码:" />
+                    <PasswordBox Grid.Row="1"
+                                 Grid.Column="1"
+                                 HorizontalAlignment="Left"
+                                 VerticalAlignment="Center"
+                                 Margin="20"
+                                 Width="200"
+                                 HorizontalContentAlignment="Left"
+                                 VerticalContentAlignment="Center"
+                                 PasswordChar="*"
+                                 Style="{StaticResource MaterialDesignFloatingHintPasswordBox}"
+                                 materialDesign:HintAssist.HelperText="{lex:Loc 请输入用户登录密码}"
+                                 materialDesign:HintAssist.Hint="{lex:Loc 密码}"
+                                 materialDesign:TextFieldAssist.HasClearButton="True"
+                                 materialDesign:PasswordBoxAssist.Password="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" />
+                    <Button Grid.Row="2"
+                            Grid.Column="0"
+                            Grid.ColumnSpan="2"
+                            HorizontalAlignment="Center"
+                            VerticalAlignment="Center"
+                            Content="{lex:Loc 登录}"
+                            Height="30"
+                            Margin="0,50"
+                            MinWidth="200"
+                            BorderThickness="0"
+                            materialDesign:ButtonAssist.CornerRadius="5"
+                            Command="{Binding LoginCommand}">
+                        <Button.Background>
+                            <LinearGradientBrush EndPoint="0.5,1"
+                                                 StartPoint="0.5,0">
+                                <GradientStop Color="#FF73B3BD"
+                                              Offset="0" />
+                                <GradientStop Color="#FF309EAF"
+                                              Offset="1" />
+                            </LinearGradientBrush>
+                        </Button.Background>
+                    </Button>
+                    <Button Grid.Row="3"
+                            Grid.Column="0"
+                            Grid.ColumnSpan="2"
+                            Height="50"
+                            Width="50"
+                            materialDesign:ButtonAssist.CornerRadius="5"
+                            HorizontalAlignment="Center"
+                            VerticalAlignment="Center"
+                            ToolTip="刷卡登录"
+                            Padding="0"
+                            Command="{Binding TransitionerCommand}"
+                            CommandParameter="0"
+                            Visibility="{Binding CardBtnVisibility}"
+                            IsEnabled="{Binding IsLogining,Converter={StaticResource InvertBooleanConverter}}">
+                        <Button.Content>
+                            <materialDesign:PackIcon Kind="CreditCardScanOutline" />
+                        </Button.Content>
+                    </Button>
+                </Grid>
+            </materialDesign:Transitioner>
+            
+        </Grid>
+    </Border>
+</Window>

+ 34 - 0
TeamAAS-VM/Views/User/CardLoginWindow.xaml.cs

@@ -0,0 +1,34 @@
+using System.Windows;
+
+namespace TeamAAS_VP.Views.User
+{
+    /// <summary>
+    /// Interaction logic for CardLoginWindow.xaml
+    /// </summary>
+    public partial class CardLoginWindow : Window
+    {
+        public CardLoginWindow()
+        {
+            InitializeComponent();
+            Loaded += (sender, e) =>
+            {
+                var vm = DataContext as ViewModels.User.CardLoginWindowViewModel;
+                vm.View = this;
+            };
+        }
+
+        public CardLoginWindow(bool isCard)
+        {
+            InitializeComponent();
+            Loaded += (sender, e) =>
+            {
+                var vm = DataContext as ViewModels.User.CardLoginWindowViewModel;
+                vm.View = this;
+            };
+            if (isCard)
+            {
+                this.NameLoginButton.Visibility = Visibility.Collapsed;
+            }
+        }
+    }
+}

+ 16 - 0
TeamAAS-VM/Views/User/LoginView.xaml

@@ -88,6 +88,22 @@
                                        Margin="20,0,0,0" />
                         </StackPanel>
                     </Button>
+                    <Button Grid.Row="4"
+                            Margin="0,55,0,0"
+                            Height="50"
+                            Width="50"
+                            materialDesign:ButtonAssist.CornerRadius="5"
+                            HorizontalAlignment="Center"
+                            VerticalAlignment="Center"
+                            ToolTip="刷卡登录"
+                            Padding="0"
+                            Command="{Binding CardLoginCommand}"
+                            Visibility="{Binding CardBtnVisibility}"
+                            IsEnabled="{Binding IsLogining,Converter={StaticResource InvertBooleanConverter}}">
+                        <Button.Content>
+                            <materialDesign:PackIcon Kind="CreditCardScanOutline" />
+                        </Button.Content>
+                    </Button>
                 </Grid>
             </materialDesign:Card>
         </Grid>