├── .gitignore ├── LICENSE ├── README.md ├── Shared ├── Appearance │ ├── BackgroundManager.cs │ ├── DictionaryManager.cs │ └── ThemeManager.cs ├── AssemblyInfo.cs ├── Assets │ ├── .net-48x48.png │ ├── databaseEmpty.png │ ├── gplv3-127x51.png │ ├── homeSetting.png │ ├── warning.png │ └── wpfui-48x48.png ├── Converters │ ├── BoolToAppearanceConverter.cs │ ├── FallbackBrushConverter.cs │ ├── InverseBooleanConverter.cs │ ├── IsCustomizedAccentColorConverter.cs │ ├── StringToBrushConverter.cs │ └── StringToImageSourceConverter.cs ├── Extensions │ └── TextBoxAttachedProperties.cs ├── Helpers │ ├── FaceRecognition.cs │ ├── FileOccupancy.cs │ ├── ImageProcess.cs │ ├── SQLiteHelper.cs │ ├── Settings.cs │ ├── UsersDb.cs │ ├── Utils.cs │ └── Win32Helper.cs ├── Models │ ├── EncodingFace.cs │ ├── PageButton.cs │ └── User.cs ├── Services │ ├── Contracts │ │ └── IWindow.cs │ └── WindowsProviderService.cs ├── Shared.csproj ├── Style │ ├── MyButton.xaml │ ├── MyCheckBox.xaml │ ├── MyScrollBar.xaml │ └── SelectableTextBlock.xaml └── branding │ ├── borrowAttitude.psd │ └── cameraEmpty.psd ├── SmartLibrary.sln ├── SmartLibrary ├── App.config ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── Assets │ ├── DynamicPic │ │ ├── dark │ │ │ ├── LoadingPictureError.png │ │ │ ├── PictureEmpty.png │ │ │ ├── cameraEmpty.jpg │ │ │ ├── cross.png │ │ │ └── tick.png │ │ └── light │ │ │ ├── LoadingPictureError.png │ │ │ ├── PictureEmpty.png │ │ │ ├── cameraEmpty.jpg │ │ │ ├── cross.png │ │ │ └── tick.png │ ├── applicationIcon-512.png │ ├── background.jpg │ ├── bluetooth-connected.png │ ├── bluetooth-disabled.png │ ├── bluetooth.png │ ├── bookinfo-512x512.png │ ├── borrowbook-512x512.png │ ├── database.png │ ├── findbook-512x512.png │ ├── homeBluetooth.png │ ├── loading.gif │ ├── right.png │ └── wrong.png ├── Converters │ ├── BookExistedConverter.cs │ ├── BoolToBorrowOrReturnTextConverter.cs │ ├── BoolToImageSourceConverter.cs │ └── BoolToOpenCameraTextConverter.cs ├── Extensions │ └── ImageDecoder.cs ├── Helpers │ ├── BooksDb.cs │ ├── ImageQueue.cs │ ├── LocalStorage.cs │ ├── Network.cs │ ├── ResourceManager.cs │ └── Utilities.cs ├── Models │ ├── BookInfo.cs │ ├── BookInfoSimple.cs │ ├── BookShelfInfo.cs │ ├── FaceInfoSimple.cs │ └── ImageQueueInfo.cs ├── Services │ └── ApplicationHostService.cs ├── SmartLibrary.csproj ├── Style │ ├── dark.xaml │ └── light.xaml ├── Usings.cs ├── ViewModels │ ├── AddBookViewModel.cs │ ├── AddUserViewModel.cs │ ├── BookManageViewModel.cs │ ├── BookshelfViewModel.cs │ ├── Borrow_Return_BookViewModel.cs │ ├── EditBookViewModel.cs │ ├── EditUserViewModel.cs │ ├── HomeViewModel.cs │ ├── MainWindowViewModel.cs │ ├── SettingsViewModel.cs │ └── UserManageViewModel.cs ├── Views │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ └── Pages │ │ ├── AddBook.xaml │ │ ├── AddBook.xaml.cs │ │ ├── AddUser.xaml │ │ ├── AddUser.xaml.cs │ │ ├── BookManage.xaml │ │ ├── BookManage.xaml.cs │ │ ├── Bookshelf.xaml │ │ ├── Bookshelf.xaml.cs │ │ ├── Borrow_Return_Book.xaml │ │ ├── Borrow_Return_Book.xaml.cs │ │ ├── EditBook.xaml │ │ ├── EditBook.xaml.cs │ │ ├── EditUser.xaml │ │ ├── EditUser.xaml.cs │ │ ├── Home.xaml │ │ ├── Home.xaml.cs │ │ ├── Settings.xaml │ │ ├── Settings.xaml.cs │ │ ├── UserManage.xaml │ │ └── UserManage.xaml.cs └── applicationIcon.ico ├── docs ├── book.png ├── reference.md ├── screenshot.webp ├── screenshot1.webp ├── screenshot2.webp ├── screenshot3.webp ├── screenshot4.webp └── screenshot5.webp └── test └── test.smartlibrary /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 4 | 5 | # DocFX 6 | docs/api/ 7 | 8 | # User-specific files 9 | *.rsuser 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Visual Studio Code files 19 | .vscode 20 | 21 | # IntelliJ IDEA files 22 | .idea 23 | 24 | # Mono auto generated files 25 | mono_crash.* 26 | 27 | # Build results 28 | [Dd]ebug/ 29 | [Dd]ebugPublic/ 30 | [Rr]elease/ 31 | [Rr]eleases/ 32 | x64/ 33 | x86/ 34 | [Aa][Rr][Mm]/ 35 | [Aa][Rr][Mm]64/ 36 | bld/ 37 | [Bb]in/ 38 | [Oo]bj/ 39 | [Ll]og/ 40 | [Ll]ogs/ 41 | 42 | # Visual Studio 2015/2017 cache/options directory 43 | .vs/ 44 | # Uncomment if you have tasks that create the project's static files in wwwroot 45 | #wwwroot/ 46 | 47 | # Visual Studio 2017 auto generated files 48 | Generated\ Files/ 49 | 50 | # MSTest test Results 51 | [Tt]est[Rr]esult*/ 52 | [Bb]uild[Ll]og.* 53 | 54 | # NUnit 55 | *.VisualState.xml 56 | TestResult.xml 57 | nunit-*.xml 58 | 59 | # Build Results of an ATL Project 60 | [Dd]ebugPS/ 61 | [Rr]eleasePS/ 62 | dlldata.c 63 | 64 | # Benchmark Results 65 | BenchmarkDotNet.Artifacts/ 66 | 67 | # .NET Core 68 | project.lock.json 69 | project.fragment.lock.json 70 | artifacts/ 71 | 72 | # StyleCop 73 | StyleCopReport.xml 74 | 75 | # Files built by Visual Studio 76 | *_i.c 77 | *_p.c 78 | *_h.h 79 | *.ilk 80 | *.meta 81 | *.obj 82 | *.iobj 83 | *.pch 84 | *.pdb 85 | *.ipdb 86 | *.pgc 87 | *.pgd 88 | *.rsp 89 | *.sbr 90 | *.tlb 91 | *.tli 92 | *.tlh 93 | *.tmp 94 | *.tmp_proj 95 | *_wpftmp.csproj 96 | *.log 97 | *.vspscc 98 | *.vssscc 99 | .builds 100 | *.pidb 101 | *.svclog 102 | *.scc 103 | 104 | # Chutzpah Test files 105 | _Chutzpah* 106 | 107 | # Visual C++ cache files 108 | ipch/ 109 | *.aps 110 | *.ncb 111 | *.opendb 112 | *.opensdf 113 | *.sdf 114 | *.cachefile 115 | *.VC.db 116 | *.VC.VC.opendb 117 | 118 | # Visual Studio profiler 119 | *.psess 120 | *.vsp 121 | *.vspx 122 | *.sap 123 | 124 | # Visual Studio Trace Files 125 | *.e2e 126 | 127 | # TFS 2012 Local Workspace 128 | $tf/ 129 | 130 | # Guidance Automation Toolkit 131 | *.gpState 132 | 133 | # ReSharper is a .NET coding add-in 134 | _ReSharper*/ 135 | *.[Rr]e[Ss]harper 136 | *.DotSettings.user 137 | 138 | # TeamCity is a build add-in 139 | _TeamCity* 140 | 141 | # DotCover is a Code Coverage Tool 142 | *.dotCover 143 | 144 | # AxoCover is a Code Coverage Tool 145 | .axoCover/* 146 | !.axoCover/settings.json 147 | 148 | # Visual Studio code coverage results 149 | *.coverage 150 | *.coveragexml 151 | 152 | # NCrunch 153 | _NCrunch_* 154 | .*crunch*.local.xml 155 | nCrunchTemp_* 156 | 157 | # MightyMoose 158 | *.mm.* 159 | AutoTest.Net/ 160 | 161 | # Web workbench (sass) 162 | .sass-cache/ 163 | 164 | # Installshield output folder 165 | [Ee]xpress/ 166 | 167 | # DocProject is a documentation generator add-in 168 | DocProject/buildhelp/ 169 | DocProject/Help/*.HxT 170 | DocProject/Help/*.HxC 171 | DocProject/Help/*.hhc 172 | DocProject/Help/*.hhk 173 | DocProject/Help/*.hhp 174 | DocProject/Help/Html2 175 | DocProject/Help/html 176 | 177 | # Click-Once directory 178 | publish/ 179 | 180 | # Publish Web Output 181 | *.[Pp]ublish.xml 182 | *.azurePubxml 183 | # Note: Comment the next line if you want to checkin your web deploy settings, 184 | # but database connection strings (with potential passwords) will be unencrypted 185 | *.pubxml 186 | *.publishproj 187 | 188 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 189 | # checkin your Azure Web App publish settings, but sensitive information contained 190 | # in these scripts will be unencrypted 191 | PublishScripts/ 192 | 193 | # NuGet Packages 194 | *.nupkg 195 | # NuGet Symbol Packages 196 | *.snupkg 197 | # The packages folder can be ignored because of Package Restore 198 | **/[Pp]ackages/* 199 | # except build/, which is used as an MSBuild target. 200 | !**/[Pp]ackages/build/ 201 | # Uncomment if necessary however generally it will be regenerated when needed 202 | #!**/[Pp]ackages/repositories.config 203 | # NuGet v3's project.json files produces more ignorable files 204 | *.nuget.props 205 | *.nuget.targets 206 | 207 | # Microsoft Azure Build Output 208 | csx/ 209 | *.build.csdef 210 | 211 | # Microsoft Azure Emulator 212 | ecf/ 213 | rcf/ 214 | 215 | # Windows Store app package directories and files 216 | AppPackages/ 217 | BundleArtifacts/ 218 | Package.StoreAssociation.xml 219 | _pkginfo.txt 220 | *.appx 221 | *.appxbundle 222 | *.appxupload 223 | 224 | # Visual Studio cache files 225 | # files ending in .cache can be ignored 226 | *.[Cc]ache 227 | # but keep track of directories ending in .cache 228 | !?*.[Cc]ache/ 229 | 230 | # Others 231 | ClientBin/ 232 | ~$* 233 | *~ 234 | *.dbmdl 235 | *.dbproj.schemaview 236 | *.jfm 237 | *.pfx 238 | *.publishsettings 239 | orleans.codegen.cs 240 | 241 | # Including strong name files can present a security risk 242 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 243 | #*.snk 244 | 245 | # Since there are multiple workflows, uncomment next line to ignore bower_components 246 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 247 | #bower_components/ 248 | 249 | # RIA/Silverlight projects 250 | Generated_Code/ 251 | 252 | # Backup & report files from converting an old project file 253 | # to a newer Visual Studio version. Backup files are not needed, 254 | # because we have git ;-) 255 | _UpgradeReport_Files/ 256 | Backup*/ 257 | UpgradeLog*.XML 258 | UpgradeLog*.htm 259 | ServiceFabricBackup/ 260 | *.rptproj.bak 261 | 262 | # SQL Server files 263 | *.mdf 264 | *.ldf 265 | *.ndf 266 | 267 | # Business Intelligence projects 268 | *.rdl.data 269 | *.bim.layout 270 | *.bim_*.settings 271 | *.rptproj.rsuser 272 | *- [Bb]ackup.rdl 273 | *- [Bb]ackup ([0-9]).rdl 274 | *- [Bb]ackup ([0-9][0-9]).rdl 275 | 276 | # Microsoft Fakes 277 | FakesAssemblies/ 278 | 279 | # GhostDoc plugin setting file 280 | *.GhostDoc.xml 281 | 282 | # Node.js Tools for Visual Studio 283 | .ntvs_analysis.dat 284 | node_modules/ 285 | 286 | # Visual Studio 6 build log 287 | *.plg 288 | 289 | # Visual Studio 6 workspace options file 290 | *.opt 291 | 292 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 293 | *.vbw 294 | 295 | # Visual Studio LightSwitch build output 296 | **/*.HTMLClient/GeneratedArtifacts 297 | **/*.DesktopClient/GeneratedArtifacts 298 | **/*.DesktopClient/ModelManifest.xml 299 | **/*.Server/GeneratedArtifacts 300 | **/*.Server/ModelManifest.xml 301 | _Pvt_Extensions 302 | 303 | # Paket dependency manager 304 | .paket/paket.exe 305 | paket-files/ 306 | 307 | # FAKE - F# Make 308 | .fake/ 309 | 310 | # CodeRush personal settings 311 | .cr/personal 312 | 313 | # Python Tools for Visual Studio (PTVS) 314 | __pycache__/ 315 | *.pyc 316 | 317 | # Cake - Uncomment if you are using it 318 | # tools/** 319 | # !tools/packages.config 320 | 321 | # Tabs Studio 322 | *.tss 323 | 324 | # Telerik's JustMock configuration file 325 | *.jmconfig 326 | 327 | # BizTalk build output 328 | *.btp.cs 329 | *.btm.cs 330 | *.odx.cs 331 | *.xsd.cs 332 | 333 | # OpenCover UI analysis results 334 | OpenCover/ 335 | 336 | # Azure Stream Analytics local run output 337 | ASALocalRun/ 338 | 339 | # MSBuild Binary and Structured Log 340 | *.binlog 341 | 342 | # NVidia Nsight GPU debugger configuration file 343 | *.nvuser 344 | 345 | # MFractors (Xamarin productivity tool) working folder 346 | .mfractor/ 347 | 348 | # Local History for Visual Studio 349 | .localhistory/ 350 | 351 | # BeatPulse healthcheck temp database 352 | healthchecksdb 353 | 354 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 355 | MigrationBackup/ 356 | 357 | # Ionide (cross platform F# VS Code tools) working folder 358 | .ionide/ 359 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 |

Smart Library

5 |

一个基于WPF和Arduino的智慧图书管理系统

6 | 7 | ## 简介 8 | 随着我国文化教育事业的飞速发展,图书馆藏书量不断增多,仅依靠人工操作已经不能高效地提供阅读服务。我们通过调研走访昆明市多家图书馆,了解到图书馆普遍存在书籍种类繁多、找书难;借阅书籍的流程繁琐,借书难;大量书籍需要清点,工作量大且效率低,归位难;现有图书馆管理系统功能单一,以机器人技术为基础,减少繁琐、重复手工操作的自动化管理流程有很大改进空间等问题。为解决这些问题,我们构思了一款采用基于 Arduino,综合应用 RFID 模块、蓝牙模块和条形码扫码等模块来实现位置引导、找书、借书、还书等功能的图书馆查、借、还智慧一体机器人。 9 | 10 | ## 研究内容和难点 11 | 我们课题小组研究的内容为:借助Arduino机器人的无线射频识别系统(RFID)和条形码扫描模块,通过扫描图书实现信息采集,实现书名或者作者等关键信息搜索,同步展示出图书相关信息,读者可以快速找到书籍和书籍相关信息(如:价格、库存、类别和存放位置等)。采用多传感器组合的机器人将实现引导、找书、借书、还书等功能。 12 | 当前研究的难点主要集中在几个方面: 13 | 1、机器人由于每一个要实现的功能都需要不同模块堆叠,随着部件的增加,工作量和设计难度也大大提高,本次课题只能围绕基础功能需求展开,在一期成果上再展开再次研究和开发。 14 | 2、课题研究所采用Arduino打造图书馆智慧机器人系统是需要打通图书馆原有管理系统,实现数据动态共享、查询、分析、回传等功能还是有一定难度。 15 | 3、智慧机器人系统基于Arduino平台,系统功率低,持续工作能力不高,可通过成果转化后实现。 16 | 17 | ## 研究目标 18 | 基于Arduino底层系统的开放性和扩展性,我们所设计的图书馆智慧机器人保有高扩展性,本课题研究基础上后续可以持续实现功能扩展及升级满足管理需求变化。 19 | 我们的设计目标是图书馆采用图书馆智慧机器人后,实现图书管理智慧化,用机器人代替人工,实现图书分拣、搬运、查重、贴码、上下架等日常操作,简化找书、借书、还书的环节,将人员从繁重传统操作中解放出来,为读者提供更加优质、便捷的图书阅读服务。 20 | 21 | ## 功能介绍 22 | 我们采用 .Net 9和 WPF 框架设计了智慧图书馆软件,用户可在软件上快速浏览或查找图书馆所有藏书详细信息,包括所在书架号、借阅状态、出版信息等。如要找书,通过软件,小车可直接引导顾客到达图书对应书架,缩短在书海中徘徊寻找时间。同时,借书、还书功能仅需通过扫描图书条形码即可完成。还书后,小车可自动将书送到对应书架前的收集框中,管理员仅需将框中的书放入书架即可。同时,该软件还具备图书管理功能,图书管理员可通过程序向本地数据库进行增、删、查、改等操作,界面美观,操作简单;以及添加图书时,可通过书籍 ISBN 号,进行图书数据联网查询,免去了大量图书信息手动录入的时间。
23 | 另外,我们还使用 C++语言编写了小车的 Arduino 主控板控制程序,通过红外巡线模块使小车按照指定路线前进;通过 PID 算法控制小车电机,使小车能够平稳前行和转弯;采用 RFID 识别技术,判断是否到达指定书架,并通过蓝牙通信与电脑交换数据,自主设计了通信数据结构,达到控制小车执行多种任务的目的,实现小车与电脑协同工作。 24 | 25 | ## 🖼 截图 26 | 27 |

主界面

28 | 29 |

书架界面

30 | 31 |

借还界面

32 | 33 |

用户管理界面

34 | 35 |

图书管理界面

36 | 37 |

设置界面

38 | 39 | ## 📐使用教程 40 | * 在图书设置界面点击导入按钮,导入test文件夹下的测试书籍 41 | * 在用户管理界面点击加号,创建一个用户 42 | * 接着就可以使用借还功能 43 | * 添加书籍需要填写API Key,用于在线查询书籍信息,在设置界面中可找到API Key的设置项,设置完即可使用在线查询功能 44 | 45 | ## 🌏 路线 46 | - [x] 增加用户管理系统 47 | - [ ] 增加 AI 个性化推荐 48 | 49 | ## ⌨️ 开发环境 50 | [Visual Studio 2022](https://visualstudio.microsoft.com/zh-hans/vs) 51 | 52 | - 系统要求 53 | - [Windows 11 版本 21H2 或更高版本:家庭版、专业版、专业教育版、专业工作站版、企业版和教育版](https://learn.microsoft.com/zh-cn/visualstudio/releases/2022/system-requirements) 54 | 55 | - 工作负荷 56 | - .NET 桌面开发(.Net 8 or 9) 57 | 58 | - 插件 59 | - [XAML Styler for Visual Studio 2022(可选)](https://marketplace.visualstudio.com/items?itemName=TeamXavalon.XAMLStyler2022) 60 | 61 | ## 💡技术分享 62 | - [可复制的TextBlock](Shared/Style/SelectableTextBlock.xaml) 63 | - [TextBox数字限定](Shared/Extensions/TextBoxAttachedProperties.cs) 64 | - [DataGrid分页](SmartLibrary/ViewModels/BookManageViewModel.cs) 65 | - [图像等比例缩放](Shared/Helpers/ImageProcess.cs) 66 | - [图片队列下载](SmartLibrary/Helpers/ImageQueue.cs) 67 | - [文件大小计算](Shared/Helpers/FileOccupancy.cs) 68 | - [本地人脸识别](Shared/Helpers/FaceRecognition.cs) 69 | - [无需管理员设置开机自启](SmartLibrary/Helpers/Utilities.cs) 70 | - [互斥锁实现程序单例及后台激活](SmartLibrary/App.xaml.cs) 71 | 72 | ## 📄 引用 73 | 详见 [./docs/reference.md](docs/reference.md) 74 | 75 | ## 图标 76 | Reading icons created by Freepik - Flaticon 77 | 78 | Refresh data icons created by Freepik - Flaticon 79 | 80 | Borrow icons created by max.icons - Flaticon 81 | 82 | Database icons created by Freepik - Flaticon 83 | 84 | Warning icons created by Freepik - Flaticon 85 | 86 | ## License 87 | SmartLibrary is licensed under [GPLv3](./LICENSE). 88 | Copyright © 2025 by Zhao Yanglei. -------------------------------------------------------------------------------- /Shared/Appearance/BackgroundManager.cs: -------------------------------------------------------------------------------- 1 | using Shared.Helpers; 2 | using System.Windows; 3 | using Wpf.Ui.Appearance; 4 | using Wpf.Ui.Controls; 5 | using Wpf.Ui.Interop; 6 | 7 | namespace Shared.Appearance 8 | { 9 | public static class BackgroundManager 10 | { 11 | /// 12 | /// Tries to apply dark theme to . 13 | /// 14 | public static void ApplyDarkThemeToWindow(Window? window) 15 | { 16 | if (window is null) 17 | { 18 | return; 19 | } 20 | 21 | if (window.IsLoaded) 22 | { 23 | _ = UnsafeNativeMethods.ApplyWindowDarkMode(window); 24 | } 25 | 26 | window.Loaded += (sender, _) => UnsafeNativeMethods.ApplyWindowDarkMode(sender as Window); 27 | } 28 | 29 | /// 30 | /// Tries to remove dark theme from . 31 | /// 32 | public static void RemoveDarkThemeFromWindow(Window? window) 33 | { 34 | if (window is null) 35 | { 36 | return; 37 | } 38 | 39 | if (window.IsLoaded) 40 | { 41 | _ = UnsafeNativeMethods.RemoveWindowDarkMode(window); 42 | } 43 | 44 | window.Loaded += (sender, _) => UnsafeNativeMethods.RemoveWindowDarkMode(sender as Window); 45 | } 46 | 47 | /// 48 | /// Forces change to application background. Required if custom background effect was previously applied. 49 | /// 50 | public static void UpdateBackground( 51 | Window? window, 52 | ApplicationTheme applicationTheme, 53 | WindowBackdropType backdrop 54 | ) 55 | { 56 | if (window is null) 57 | { 58 | return; 59 | } 60 | 61 | _ = WindowBackdrop.RemoveBackdrop(window); 62 | 63 | if (applicationTheme == ApplicationTheme.HighContrast) 64 | { 65 | backdrop = WindowBackdropType.None; 66 | } 67 | 68 | // This was required to update the background when moving from a HC theme to light/dark theme. However, this breaks theme proper light/dark theme changing on Windows 10. 69 | // But window backdrop effects are not applied when it has an opaque (or any) background on W11 (so removing this breaks backdrop effects when switching themes), however, for legacy MICA it may not be required 70 | // using existing variable, though the OS build which (officially) supports setting DWM_SYSTEMBACKDROP_TYPE attribute is build 22621 71 | // source: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type 72 | if (Utils.IsOSWindows11Insider1OrNewer && backdrop is not WindowBackdropType.None) 73 | { 74 | _ = WindowBackdrop.RemoveBackground(window); 75 | } 76 | 77 | _ = WindowBackdrop.ApplyBackdrop(window, backdrop); 78 | 79 | if (applicationTheme is ApplicationTheme.Dark) 80 | { 81 | ApplyDarkThemeToWindow(window); 82 | } 83 | else 84 | { 85 | RemoveDarkThemeFromWindow(window); 86 | } 87 | 88 | _ = WindowBackdrop.RemoveTitlebarBackground(window); 89 | 90 | foreach (object? subWindow in window.OwnedWindows) 91 | { 92 | if (subWindow is Window windowSubWindow) 93 | { 94 | _ = WindowBackdrop.ApplyBackdrop(windowSubWindow, backdrop); 95 | 96 | if (applicationTheme is ApplicationTheme.Dark) 97 | { 98 | ApplyDarkThemeToWindow(windowSubWindow); 99 | } 100 | else 101 | { 102 | RemoveDarkThemeFromWindow(windowSubWindow); 103 | } 104 | 105 | _ = WindowBackdrop.RemoveTitlebarBackground(window); 106 | } 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Shared/Appearance/DictionaryManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Windows; 3 | using Wpf.Ui; 4 | 5 | namespace Shared.Appearance 6 | { 7 | internal class DictionaryManager 8 | { 9 | /// 10 | /// Gets the namespace, e.g. the library the resource is being searched for. 11 | /// 12 | public string SearchNamespace { get; } 13 | 14 | public DictionaryManager(string searchNamespace) 15 | { 16 | SearchNamespace = searchNamespace; 17 | } 18 | 19 | /// 20 | /// Shows whether the application contains the . 21 | /// 22 | /// Any part of the resource name. 23 | /// if it doesn't exist. 24 | public bool HasDictionary(string resourceLookup) 25 | { 26 | return GetDictionary(resourceLookup) != null; 27 | } 28 | 29 | /// 30 | /// Gets the if exists. 31 | /// 32 | /// Any part of the resource name. 33 | /// , if it doesn't exist. 34 | public ResourceDictionary? GetDictionary(string resourceLookup) 35 | { 36 | Collection applicationDictionaries = GetApplicationMergedDictionaries(); 37 | 38 | if (applicationDictionaries.Count == 0) 39 | { 40 | return null; 41 | } 42 | 43 | foreach (ResourceDictionary t in applicationDictionaries) 44 | { 45 | string resourceDictionaryUri; 46 | 47 | if (t?.Source != null) 48 | { 49 | resourceDictionaryUri = t.Source.ToString(); 50 | 51 | if ( 52 | resourceDictionaryUri.Contains(SearchNamespace, StringComparison.OrdinalIgnoreCase) 53 | && resourceDictionaryUri.Contains(resourceLookup, StringComparison.OrdinalIgnoreCase) 54 | ) 55 | { 56 | return t; 57 | } 58 | } 59 | 60 | foreach (ResourceDictionary? t1 in t!.MergedDictionaries) 61 | { 62 | if (t1?.Source == null) 63 | { 64 | continue; 65 | } 66 | 67 | resourceDictionaryUri = t1.Source.ToString(); 68 | 69 | if ( 70 | !resourceDictionaryUri.Contains(SearchNamespace, StringComparison.OrdinalIgnoreCase) 71 | || !resourceDictionaryUri.Contains(resourceLookup, StringComparison.OrdinalIgnoreCase) 72 | ) 73 | { 74 | continue; 75 | } 76 | 77 | return t1; 78 | } 79 | } 80 | 81 | return null; 82 | } 83 | 84 | /// 85 | /// Shows whether the application contains the . 86 | /// 87 | /// Any part of the resource name. 88 | /// A valid for the replaced resource. 89 | /// if the dictionary was updated. otherwise. 90 | public bool UpdateDictionary(string resourceLookup, Uri? newResourceUri) 91 | { 92 | Collection applicationDictionaries = UiApplication 93 | .Current 94 | .Resources 95 | .MergedDictionaries; 96 | 97 | if (applicationDictionaries.Count == 0 || newResourceUri is null) 98 | { 99 | return false; 100 | } 101 | 102 | for (var i = 0; i < applicationDictionaries.Count; i++) 103 | { 104 | string sourceUri; 105 | 106 | if (applicationDictionaries[i]?.Source != null) 107 | { 108 | sourceUri = applicationDictionaries[i].Source.ToString(); 109 | 110 | if ( 111 | sourceUri.Contains(SearchNamespace, StringComparison.OrdinalIgnoreCase) 112 | && sourceUri.Contains(resourceLookup, StringComparison.OrdinalIgnoreCase) 113 | ) 114 | { 115 | applicationDictionaries[i] = new() { Source = newResourceUri }; 116 | 117 | return true; 118 | } 119 | } 120 | 121 | for (var j = 0; j < applicationDictionaries[i].MergedDictionaries.Count; j++) 122 | { 123 | if (applicationDictionaries[i].MergedDictionaries[j]?.Source == null) 124 | { 125 | continue; 126 | } 127 | 128 | sourceUri = applicationDictionaries[i].MergedDictionaries[j].Source.ToString(); 129 | 130 | if ( 131 | !sourceUri.Contains(SearchNamespace, StringComparison.OrdinalIgnoreCase) 132 | || !sourceUri.Contains(resourceLookup, StringComparison.OrdinalIgnoreCase) 133 | ) 134 | { 135 | continue; 136 | } 137 | 138 | applicationDictionaries[i].MergedDictionaries[j] = new() { Source = newResourceUri }; 139 | 140 | return true; 141 | } 142 | } 143 | 144 | return false; 145 | } 146 | 147 | private Collection GetApplicationMergedDictionaries() 148 | { 149 | return UiApplication.Current.Resources.MergedDictionaries; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Shared/Appearance/ThemeManager.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using Wpf.Ui; 3 | using Wpf.Ui.Appearance; 4 | using Wpf.Ui.Controls; 5 | 6 | namespace Shared.Appearance 7 | { 8 | public static class ThemeManager 9 | { 10 | private static ApplicationTheme _cachedApplicationTheme = ApplicationTheme.Unknown; 11 | 12 | internal const string LibraryNamespace = "wpf.ui;"; 13 | 14 | internal const string ThemesDictionaryPath = "pack://application:,,,/Wpf.Ui;component/Resources/Theme/"; 15 | 16 | /// 17 | /// Event triggered when the application's theme is changed. 18 | /// 19 | public static event ThemeChangedEvent? Changed; 20 | 21 | /// 22 | /// Gets a value that indicates whether the application is currently using the high contrast theme. 23 | /// 24 | /// if application uses high contrast theme. 25 | public static bool IsHighContrast() => _cachedApplicationTheme == ApplicationTheme.HighContrast; 26 | 27 | /// 28 | /// Gets a value that indicates whether the Windows is currently using the high contrast theme. 29 | /// 30 | /// if system uses high contrast theme. 31 | public static bool IsSystemHighContrast() => SystemThemeManager.HighContrast; 32 | 33 | /// 34 | /// Changes the current application theme. 35 | /// 36 | /// Theme to set. 37 | /// Whether the custom background effect should be applied. 38 | /// Whether the color accents should be changed. 39 | public static void Apply( 40 | ApplicationTheme applicationTheme, 41 | WindowBackdropType backgroundEffect = WindowBackdropType.Mica, 42 | bool updateAccent = true 43 | ) 44 | { 45 | if (updateAccent) 46 | { 47 | ApplicationAccentColorManager.Apply( 48 | ApplicationAccentColorManager.GetColorizationColor(), 49 | applicationTheme, 50 | false 51 | ); 52 | } 53 | 54 | if (applicationTheme == ApplicationTheme.Unknown) 55 | { 56 | return; 57 | } 58 | 59 | DictionaryManager appDictionaries = new(LibraryNamespace); 60 | 61 | string themeDictionaryName = "Light"; 62 | 63 | switch (applicationTheme) 64 | { 65 | case ApplicationTheme.Dark: 66 | themeDictionaryName = "Dark"; 67 | break; 68 | case ApplicationTheme.HighContrast: 69 | themeDictionaryName = ApplicationThemeManager.GetSystemTheme() switch 70 | { 71 | SystemTheme.HC1 => "HC1", 72 | SystemTheme.HC2 => "HC2", 73 | SystemTheme.HCBlack => "HCBlack", 74 | SystemTheme.HCWhite => "HCWhite", 75 | _ => "HCWhite", 76 | }; 77 | break; 78 | } 79 | 80 | bool isUpdated = appDictionaries.UpdateDictionary( 81 | "theme", 82 | new Uri(ThemesDictionaryPath + themeDictionaryName + ".xaml", UriKind.Absolute) 83 | ); 84 | 85 | System.Diagnostics.Debug.WriteLine( 86 | $"INFO | {typeof(ApplicationThemeManager)} tries to update theme to {themeDictionaryName} ({applicationTheme}): {isUpdated}", 87 | nameof(ApplicationThemeManager) 88 | ); 89 | 90 | if (!isUpdated) 91 | { 92 | return; 93 | } 94 | 95 | SystemThemeManager.UpdateSystemThemeCache(); 96 | 97 | _cachedApplicationTheme = applicationTheme; 98 | 99 | Changed?.Invoke(applicationTheme, ApplicationAccentColorManager.SystemAccent); 100 | 101 | if (UiApplication.Current.MainWindow is Window mainWindow) 102 | { 103 | WindowBackgroundManager.UpdateBackground(mainWindow, applicationTheme, backgroundEffect); 104 | } 105 | } 106 | 107 | /// 108 | /// Applies Resources in the . 109 | /// 110 | public static void Apply(FrameworkElement frameworkElement) 111 | { 112 | if (frameworkElement is null) 113 | { 114 | return; 115 | } 116 | 117 | ResourceDictionary[] resourcesRemove = frameworkElement 118 | .Resources.MergedDictionaries.Where(e => e.Source is not null) 119 | .Where(e => e.Source.ToString().Contains(LibraryNamespace, StringComparison.OrdinalIgnoreCase)) 120 | .ToArray(); 121 | 122 | foreach (ResourceDictionary? resource in UiApplication.Current.Resources.MergedDictionaries) 123 | { 124 | System.Diagnostics.Debug.WriteLine( 125 | $"INFO | {typeof(ApplicationThemeManager)} Add {resource.Source}", 126 | "Wpf.Ui.Appearance" 127 | ); 128 | frameworkElement.Resources.MergedDictionaries.Add(resource); 129 | } 130 | 131 | foreach (ResourceDictionary resource in resourcesRemove) 132 | { 133 | System.Diagnostics.Debug.WriteLine( 134 | $"INFO | {typeof(ApplicationThemeManager)} Remove {resource.Source}", 135 | "Wpf.Ui.Appearance" 136 | ); 137 | 138 | _ = frameworkElement.Resources.MergedDictionaries.Remove(resource); 139 | } 140 | 141 | foreach (System.Collections.DictionaryEntry resource in UiApplication.Current.Resources) 142 | { 143 | System.Diagnostics.Debug.WriteLine( 144 | $"INFO | {typeof(ApplicationThemeManager)} Copy Resource {resource.Key} - {resource.Value}", 145 | "Wpf.Ui.Appearance" 146 | ); 147 | frameworkElement.Resources[resource.Key] = resource.Value; 148 | } 149 | } 150 | 151 | public static void ApplySystemTheme() 152 | { 153 | ApplySystemTheme(true); 154 | } 155 | 156 | public static void ApplySystemTheme(bool updateAccent) 157 | { 158 | SystemThemeManager.UpdateSystemThemeCache(); 159 | 160 | SystemTheme systemTheme = GetSystemTheme(); 161 | 162 | ApplicationTheme themeToSet = ApplicationTheme.Light; 163 | 164 | if (systemTheme is SystemTheme.Dark or SystemTheme.CapturedMotion or SystemTheme.Glow) 165 | { 166 | themeToSet = ApplicationTheme.Dark; 167 | } 168 | else if ( 169 | systemTheme is SystemTheme.HC1 or SystemTheme.HC2 or SystemTheme.HCBlack or SystemTheme.HCWhite 170 | ) 171 | { 172 | themeToSet = ApplicationTheme.HighContrast; 173 | } 174 | 175 | Apply(themeToSet, updateAccent: updateAccent); 176 | } 177 | 178 | /// 179 | /// Gets currently set application theme. 180 | /// 181 | /// if something goes wrong. 182 | public static ApplicationTheme GetAppTheme() 183 | { 184 | if (_cachedApplicationTheme == ApplicationTheme.Unknown) 185 | { 186 | FetchApplicationTheme(); 187 | } 188 | 189 | return _cachedApplicationTheme; 190 | } 191 | 192 | /// 193 | /// Gets currently set system theme. 194 | /// 195 | /// if something goes wrong. 196 | public static SystemTheme GetSystemTheme() 197 | { 198 | return SystemThemeManager.GetCachedSystemTheme(); 199 | } 200 | 201 | /// 202 | /// Gets a value that indicates whether the application is matching the system theme. 203 | /// 204 | /// if the application has the same theme as the system. 205 | public static bool IsAppMatchesSystem() 206 | { 207 | ApplicationTheme appApplicationTheme = GetAppTheme(); 208 | SystemTheme sysTheme = GetSystemTheme(); 209 | 210 | return appApplicationTheme switch 211 | { 212 | ApplicationTheme.Dark => sysTheme 213 | is SystemTheme.Dark 214 | or SystemTheme.CapturedMotion 215 | or SystemTheme.Glow, 216 | ApplicationTheme.Light => sysTheme 217 | is SystemTheme.Light 218 | or SystemTheme.Flow 219 | or SystemTheme.Sunrise, 220 | _ => appApplicationTheme == ApplicationTheme.HighContrast && SystemThemeManager.HighContrast, 221 | }; 222 | } 223 | 224 | /// 225 | /// Checks if the application and the operating system are currently working in a dark theme. 226 | /// 227 | public static bool IsMatchedDark() 228 | { 229 | ApplicationTheme appApplicationTheme = GetAppTheme(); 230 | SystemTheme sysTheme = GetSystemTheme(); 231 | 232 | if (appApplicationTheme != ApplicationTheme.Dark) 233 | { 234 | return false; 235 | } 236 | 237 | return sysTheme is SystemTheme.Dark or SystemTheme.CapturedMotion or SystemTheme.Glow; 238 | } 239 | 240 | /// 241 | /// Checks if the application and the operating system are currently working in a light theme. 242 | /// 243 | public static bool IsMatchedLight() 244 | { 245 | ApplicationTheme appApplicationTheme = GetAppTheme(); 246 | SystemTheme sysTheme = GetSystemTheme(); 247 | 248 | if (appApplicationTheme != ApplicationTheme.Light) 249 | { 250 | return false; 251 | } 252 | 253 | return sysTheme is SystemTheme.Light or SystemTheme.Flow or SystemTheme.Sunrise; 254 | } 255 | 256 | /// 257 | /// Tries to guess the currently set application theme. 258 | /// 259 | private static void FetchApplicationTheme() 260 | { 261 | DictionaryManager appDictionaries = new(LibraryNamespace); 262 | ResourceDictionary? themeDictionary = appDictionaries.GetDictionary("theme"); 263 | 264 | if (themeDictionary == null) 265 | { 266 | return; 267 | } 268 | 269 | string themeUri = themeDictionary.Source.ToString(); 270 | 271 | if (themeUri.Contains("light", StringComparison.OrdinalIgnoreCase)) 272 | { 273 | _cachedApplicationTheme = ApplicationTheme.Light; 274 | } 275 | 276 | if (themeUri.Contains("dark", StringComparison.OrdinalIgnoreCase)) 277 | { 278 | _cachedApplicationTheme = ApplicationTheme.Dark; 279 | } 280 | 281 | if (themeUri.Contains("highcontrast", StringComparison.OrdinalIgnoreCase)) 282 | { 283 | _cachedApplicationTheme = ApplicationTheme.HighContrast; 284 | } 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /Shared/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /Shared/Assets/.net-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/.net-48x48.png -------------------------------------------------------------------------------- /Shared/Assets/databaseEmpty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/databaseEmpty.png -------------------------------------------------------------------------------- /Shared/Assets/gplv3-127x51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/gplv3-127x51.png -------------------------------------------------------------------------------- /Shared/Assets/homeSetting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/homeSetting.png -------------------------------------------------------------------------------- /Shared/Assets/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/warning.png -------------------------------------------------------------------------------- /Shared/Assets/wpfui-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/wpfui-48x48.png -------------------------------------------------------------------------------- /Shared/Converters/BoolToAppearanceConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace Shared.Converters 5 | { 6 | public sealed class BoolToAppearanceConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 9 | { 10 | if ((bool)value) 11 | { 12 | return "Primary"; 13 | } 14 | else 15 | { 16 | return "Secondary"; 17 | } 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Shared/Converters/FallbackBrushConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using System.Windows.Media; 4 | 5 | namespace Shared.Converters; 6 | 7 | public class FallbackBrushConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | return value switch 12 | { 13 | SolidColorBrush brush => brush, 14 | Color color => new SolidColorBrush(color), 15 | _ => new SolidColorBrush(Colors.Red) 16 | }; 17 | } 18 | 19 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Shared/Converters/InverseBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace Shared.Converters 5 | { 6 | public sealed class InverseBooleanConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 9 | { 10 | return !(bool)value; 11 | } 12 | 13 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Shared/Converters/IsCustomizedAccentColorConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace Shared.Converters 5 | { 6 | public sealed class IsCustomizedAccentColorConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 9 | { 10 | if ((bool)value) 11 | { 12 | if ((string)parameter == "System") 13 | { 14 | return false; 15 | } 16 | else 17 | { 18 | return true; 19 | } 20 | } 21 | else 22 | { 23 | if ((string)parameter == "System") 24 | { 25 | return true; 26 | } 27 | else 28 | { 29 | return false; 30 | } 31 | } 32 | } 33 | 34 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 35 | { 36 | if ((string)parameter == "System") 37 | { 38 | return false; 39 | } 40 | else 41 | { 42 | return true; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Shared/Converters/StringToBrushConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using System.Windows.Media; 4 | 5 | namespace Shared.Converters 6 | { 7 | public sealed class StringToBrushConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | return new SolidColorBrush((Color)ColorConverter.ConvertFromString((string)value)); 12 | } 13 | 14 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Shared/Converters/StringToImageSourceConverter.cs: -------------------------------------------------------------------------------- 1 | using Shared.Helpers; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace Shared.Converters 6 | { 7 | public sealed class StringToImageSourceConverter : IValueConverter 8 | { 9 | public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | return ImageProcess.StringToBitmapImage((string)value); 12 | } 13 | 14 | public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | return null; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Shared/Extensions/TextBoxAttachedProperties.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using System.Windows.Input; 5 | 6 | namespace Shared.Extensions 7 | { 8 | public partial class TextBoxAttachedProperties 9 | { 10 | [GeneratedRegex("[^0-9]+")] 11 | private static partial Regex MyRegex(); 12 | 13 | public static bool GetIsOnlyNumber(DependencyObject obj) 14 | { 15 | return (bool)obj.GetValue(IsOnlyNumberProperty); 16 | } 17 | 18 | public static void SetIsOnlyNumber(DependencyObject obj, bool value) 19 | { 20 | obj.SetValue(IsOnlyNumberProperty, value); 21 | } 22 | 23 | public static readonly DependencyProperty IsOnlyNumberProperty = 24 | DependencyProperty.RegisterAttached("IsOnlyNumber", typeof(bool), typeof(TextBox), new PropertyMetadata(false, 25 | (s, e) => 26 | { 27 | if (s is TextBox textBox) 28 | { 29 | textBox.SetValue(InputMethod.IsInputMethodEnabledProperty, !(bool)e.NewValue); 30 | textBox.PreviewTextInput -= TxtInput; 31 | if ((bool)e.NewValue) 32 | { 33 | textBox.PreviewTextInput += TxtInput; 34 | } 35 | } 36 | })); 37 | 38 | private static void TxtInput(object sender, TextCompositionEventArgs e) 39 | { 40 | e.Handled = MyRegex().IsMatch(e.Text); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Shared/Helpers/FaceRecognition.cs: -------------------------------------------------------------------------------- 1 | using Hompus.VideoInputDevices; 2 | using Newtonsoft.Json; 3 | using OpenCvSharp; 4 | using Shared.Models; 5 | using System.Drawing; 6 | using System.Windows.Media.Imaging; 7 | using ViewFaceCore; 8 | using ViewFaceCore.Configs; 9 | using ViewFaceCore.Core; 10 | using ViewFaceCore.Model; 11 | 12 | namespace Shared.Helpers 13 | { 14 | public static class FaceRecognition 15 | { 16 | private static VideoCapture videoCapture = new(); 17 | private static readonly FaceDetector faceDetector; 18 | private static readonly FaceLandmarker faceMark; 19 | private static readonly FaceRecognizer faceRecognizer = new(); 20 | private static readonly Pen pen = new(Color.Red, 2); 21 | private static readonly float videoCaptureFps = 18; 22 | 23 | public static bool IsCameraOpened { get; set; } 24 | 25 | public static int SleepTime 26 | { 27 | get 28 | { 29 | return (int)Math.Round(1000 / videoCaptureFps); 30 | } 31 | } 32 | 33 | public static Dictionary SystemCameraDevices 34 | { 35 | get 36 | { 37 | using SystemDeviceEnumerator sde = new(); 38 | Dictionary devices = []; 39 | try 40 | { 41 | IReadOnlyDictionary systemDevices = sde.ListVideoInputDevice(); 42 | foreach (var device in systemDevices) 43 | { 44 | devices.Add(device.Value, device.Key); 45 | } 46 | } 47 | catch 48 | { 49 | devices.Add("暂无摄像头", -1); 50 | } 51 | return devices; 52 | } 53 | } 54 | 55 | static FaceRecognition() 56 | { 57 | FaceDetectConfig config = new() 58 | { 59 | FaceSize = 80 60 | }; 61 | faceDetector = new(config); 62 | 63 | FaceLandmarkConfig faceLandmarkconfig = new() 64 | { 65 | MarkType = MarkType.Light, 66 | DeviceType = DeviceType.CPU 67 | }; 68 | faceMark = new(faceLandmarkconfig); 69 | } 70 | 71 | public static Bitmap GetImage() 72 | { 73 | using Mat v_mat = new(); 74 | videoCapture.Read(v_mat); 75 | if (v_mat != null) 76 | { 77 | return (Bitmap)Image.FromStream(v_mat.ToMemoryStream()); 78 | } 79 | else 80 | { 81 | return new Bitmap(2, 2); 82 | } 83 | } 84 | 85 | public static Bitmap GetMaskImage(Bitmap cameraImage) 86 | { 87 | Bitmap mask = new(cameraImage.Width, cameraImage.Height); 88 | using Graphics maskGraphics = Graphics.FromImage(mask); 89 | 90 | FaceInfo[] faceInfos = faceDetector.Detect(cameraImage); 91 | foreach (FaceInfo face in faceInfos) 92 | { 93 | maskGraphics.DrawRectangle(pen, face.Location.X, face.Location.Y, face.Location.Width, face.Location.Height); 94 | } 95 | return mask; 96 | } 97 | 98 | public static (Bitmap, float[], int, int) GetMaskAndName(Bitmap cameraImage) 99 | { 100 | Bitmap mask = new(cameraImage.Width, cameraImage.Height); 101 | using Graphics maskGraphics = Graphics.FromImage(mask); 102 | 103 | FaceInfo[] faceInfos = faceDetector.Detect(cameraImage); 104 | if (faceInfos.Length > 0) 105 | { 106 | FaceInfo faceInfo = faceInfos[0]; 107 | FaceMarkPoint[] faceMarkPoint = faceMark.Mark(cameraImage, faceInfo); 108 | 109 | maskGraphics.DrawRectangle(pen, faceInfo.Location.X, faceInfo.Location.Y, faceInfo.Location.Width, faceInfo.Location.Height); 110 | 111 | float[] feature = faceRecognizer.Extract(cameraImage, faceMarkPoint); 112 | return (mask, feature, faceInfo.Location.X, faceInfo.Location.Y); 113 | } 114 | else 115 | { 116 | return (mask, [], 0, 0); 117 | } 118 | 119 | } 120 | 121 | public async static ValueTask GetFace(Bitmap cameraImage) 122 | { 123 | FaceInfo[] faceInfos = await faceDetector.DetectAsync(cameraImage); 124 | 125 | if (faceInfos.Length > 0) 126 | { 127 | FaceInfo faceInfo = faceInfos[0]; 128 | FaceMarkPoint[] faceMarkPoint = faceMark.Mark(cameraImage, faceInfo); 129 | 130 | Rectangle cropArea = new(faceInfo.Location.X, faceInfo.Location.Y, faceInfo.Location.Width, faceInfo.Location.Height); 131 | using Bitmap croppedBitmap = cameraImage.Clone(cropArea, cameraImage.PixelFormat); 132 | Bitmap a = ImageProcess.Resize(croppedBitmap, 164, 216); 133 | 134 | return new EncodingFace(cameraImage, ImageProcess.BitmapToBitmapImage(a), faceInfo, faceMarkPoint); 135 | } 136 | else 137 | { 138 | return new EncodingFace(new Bitmap(2, 2), new BitmapImage(), new FaceInfo(), []); 139 | } 140 | } 141 | 142 | public static List GetFaceQualitys(List faces) 143 | { 144 | using FaceQuality faceQuality = new(new()); 145 | List faceQualitys = []; 146 | foreach (EncodingFace face in faces) 147 | { 148 | faceQualitys.Add(faceQuality.Detect(face.FullImage, face.FaceInfo, face.FaceMarkPoints, QualityType.Pose).Score); 149 | } 150 | return faceQualitys; 151 | } 152 | 153 | public static float[] GetFaceFeature(EncodingFace face) 154 | { 155 | return faceRecognizer.Extract(face.FullImage, face.FaceMarkPoints); 156 | } 157 | 158 | public static string GetFaceFeatureString(EncodingFace face) 159 | { 160 | return JsonConvert.SerializeObject(GetFaceFeature(face)); 161 | } 162 | 163 | public static float[] GetFaceFeatureFromString(string faceFeature) 164 | { 165 | return JsonConvert.DeserializeObject(faceFeature) ?? []; 166 | } 167 | 168 | public static bool IsSelf(float[] a, float[] b) 169 | { 170 | return faceRecognizer.IsSelf(a, b); 171 | } 172 | 173 | public static bool OpenCamera(int videoCapture_id, out string message) 174 | { 175 | videoCapture = new(); 176 | videoCapture.Set(VideoCaptureProperties.FrameWidth, 960); 177 | videoCapture.Set(VideoCaptureProperties.FrameHeight, 564);//高度 178 | videoCapture.Set(VideoCaptureProperties.Fps, (int)videoCaptureFps); 179 | try 180 | { 181 | message = string.Empty; 182 | videoCapture.Open(videoCapture_id, VideoCaptureAPIs.DSHOW); 183 | if (videoCapture.IsOpened()) 184 | { 185 | IsCameraOpened = true; 186 | return true; 187 | } 188 | return false; 189 | } 190 | catch (Exception e) 191 | { 192 | { 193 | message = e.Message; 194 | return false; 195 | } 196 | } 197 | } 198 | 199 | public static void CloseCamera() 200 | { 201 | if (videoCapture.IsOpened()) 202 | { 203 | IsCameraOpened = false; 204 | videoCapture.Release(); 205 | } 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /Shared/Helpers/FileOccupancy.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Shared.Helpers 4 | { 5 | public static class FileOccupancy 6 | { 7 | public static string GetDirectorySize(string path) 8 | { 9 | if (Directory.Exists(path)) 10 | { 11 | DirectoryInfo folder = new(path); 12 | return FormatBytes(FolderSize(folder)); 13 | } 14 | else 15 | { 16 | return "0 bytes"; 17 | } 18 | } 19 | 20 | public static string GetFileSize(string path) 21 | { 22 | if (File.Exists(path)) 23 | { 24 | FileInfo fileInfo = new(path); 25 | return FormatBytes(fileInfo.Length); 26 | } 27 | else 28 | { 29 | return "0 bytes"; 30 | } 31 | } 32 | 33 | private static long FolderSize(DirectoryInfo folder) 34 | { 35 | long totalSizeOfDir = 0; 36 | 37 | // 获取目录中的所有文件 38 | FileInfo[] allFiles = folder.GetFiles(); 39 | 40 | // 循环遍历每个文件并获取其大小 41 | foreach (FileInfo file in allFiles) 42 | { 43 | totalSizeOfDir += file.Length; 44 | 45 | // 在这里计算长度。 46 | } 47 | 48 | DirectoryInfo[] subFolders = folder.GetDirectories(); 49 | 50 | // 在这里,我们查看文件中是否存在子文件夹或目录。 51 | foreach (DirectoryInfo dir in subFolders) 52 | { 53 | totalSizeOfDir += FolderSize(dir); 54 | 55 | // 在这里,我们递归调用来检查所有子文件夹。 56 | } 57 | return totalSizeOfDir; 58 | 59 | // 我们返回总大小在这里。 60 | } 61 | 62 | private static string FormatBytes(long bytes) 63 | { 64 | /*此方法基本上用于确定文件的大小。它首先确定我们是否必须在字节,KB,MB或GB中完成。如果大小在1KB和1MB之间,那么我们将计算KB的大小。同样,如果在MB和GB之间,则将其计算为MB。*/ 65 | string[] sizes = ["bytes", "KB", "MB", "GB", "TB"]; 66 | 67 | // 在这里,我们将所有大小存储在字符串中。 68 | int order = 0; 69 | 70 | // 我们使用零初始化顺序,以便它不返回一些垃圾值。 71 | while (bytes >= 1024 && order < sizes.Length - 1) 72 | { 73 | order++; 74 | bytes /= 1024; 75 | } 76 | return $"{bytes:0.##} {sizes[order]}"; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Shared/Helpers/ImageProcess.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | using System.IO; 4 | using System.Windows; 5 | using System.Windows.Media; 6 | using System.Windows.Media.Imaging; 7 | 8 | namespace Shared.Helpers 9 | { 10 | public static class ImageProcess 11 | { 12 | public static BitmapImage? StringToBitmapImage(string path) 13 | { 14 | BitmapImage? bitmapImage = null; 15 | if (!string.IsNullOrEmpty(path)) 16 | { 17 | if (path.StartsWith("pack")) 18 | { 19 | bitmapImage = new(new Uri(path)); 20 | } 21 | else 22 | { 23 | using BinaryReader reader = new(File.Open(path, FileMode.Open)); 24 | FileInfo fi = new(path); 25 | byte[] bytes = reader.ReadBytes((int)fi.Length); 26 | reader.Close(); 27 | bitmapImage = new() 28 | { 29 | CacheOption = BitmapCacheOption.OnLoad 30 | }; 31 | bitmapImage.BeginInit(); 32 | bitmapImage.StreamSource = new MemoryStream(bytes); 33 | bitmapImage.EndInit(); 34 | } 35 | if (bitmapImage.CanFreeze) 36 | { 37 | bitmapImage.Freeze(); 38 | } 39 | } 40 | return bitmapImage; 41 | } 42 | 43 | public static Bitmap Resize(Image image, int maxWidth, int maxHeight) 44 | { 45 | //模版的宽高比例 46 | double resultRate = (double)maxWidth / maxHeight; 47 | //原图片的宽高比例 48 | double initRate = (double)image.Width / image.Height; 49 | 50 | //原图与模版比例相等,直接缩放 51 | if (resultRate == initRate) 52 | { 53 | //按模版大小生成最终图片 54 | Bitmap resultImage = new(maxWidth, maxHeight); 55 | using Graphics graphics = Graphics.FromImage(resultImage); 56 | graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High; 57 | graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 58 | graphics.Clear(System.Drawing.Color.Transparent); 59 | graphics.DrawImage(image, new Rectangle(-1, -1, maxWidth, maxHeight), new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel); 60 | return resultImage; 61 | } 62 | //原图与模版比例不等,裁剪后缩放 63 | else 64 | { 65 | //裁剪对象 66 | Bitmap tempImage; 67 | Graphics tempGraphics; 68 | 69 | //定位 70 | Rectangle fromR = new(0, 0, 0, 0);//原图裁剪定位 71 | Rectangle toR = new(0, 0, 0, 0);//目标定位 72 | 73 | //宽为标准进行裁剪 74 | if (resultRate > initRate) 75 | { 76 | //裁剪对象实例化 77 | tempImage = new Bitmap(image.Width, (int)Math.Floor(image.Width / resultRate)); 78 | tempGraphics = Graphics.FromImage(tempImage); 79 | 80 | //裁剪源定位 81 | fromR.X = 0; 82 | fromR.Y = (int)Math.Floor((image.Height - image.Width / resultRate) / 2); 83 | fromR.Width = image.Width; 84 | fromR.Height = (int)Math.Floor(image.Width / resultRate); 85 | 86 | //裁剪目标定位 87 | toR.X = 0; 88 | toR.Y = 0; 89 | toR.Width = image.Width; 90 | toR.Height = (int)Math.Floor(image.Width / resultRate); 91 | } 92 | //高为标准进行裁剪 93 | else 94 | { 95 | tempImage = new Bitmap((int)Math.Floor(image.Height * resultRate), image.Height); 96 | tempGraphics = Graphics.FromImage(tempImage); 97 | 98 | fromR.X = (int)Math.Floor((image.Width - image.Height * resultRate) / 2); 99 | fromR.Y = 0; 100 | fromR.Width = (int)Math.Floor(image.Height * resultRate); 101 | fromR.Height = image.Height; 102 | 103 | toR.X = 0; 104 | toR.Y = 0; 105 | toR.Width = (int)Math.Floor(image.Height * resultRate); 106 | toR.Height = image.Height; 107 | } 108 | 109 | //设置质量 110 | tempGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; 111 | tempGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 112 | 113 | //裁剪 114 | tempGraphics.DrawImage(image, toR, fromR, GraphicsUnit.Pixel); 115 | 116 | //按模版大小生成最终图片 117 | Bitmap resultImage = new(maxWidth, maxHeight); 118 | using Graphics resultGraphics = Graphics.FromImage(resultImage); 119 | resultGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High; 120 | resultGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 121 | resultGraphics.Clear(System.Drawing.Color.Transparent); 122 | resultGraphics.DrawImage(tempImage, new Rectangle(0, 0, maxWidth, maxHeight), new Rectangle(0, 0, tempImage.Width, tempImage.Height), GraphicsUnit.Pixel); 123 | 124 | tempGraphics.Dispose(); 125 | tempImage.Dispose(); 126 | return resultImage; 127 | } 128 | } 129 | 130 | public static void DrawText(this Bitmap image, string text, int x, int y) 131 | { 132 | using Graphics graphics = Graphics.FromImage(image); 133 | using Font font = new("Microsoft YaHei", 16); 134 | using SolidBrush brush = new(System.Drawing.Color.Red); 135 | graphics.DrawString(text, font, brush, x - 6, y - 40); 136 | } 137 | 138 | public static Bitmap ImageSourceToBitmap(ImageSource imageSource) 139 | { 140 | BitmapSource m = (BitmapSource)imageSource; 141 | 142 | Bitmap bmp = new(m.PixelWidth, m.PixelHeight, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); 143 | 144 | BitmapData data = bmp.LockBits( 145 | new Rectangle(System.Drawing.Point.Empty, bmp.Size), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); 146 | 147 | m.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride); bmp.UnlockBits(data); 148 | 149 | return bmp; 150 | } 151 | 152 | public static BitmapImage BitmapToBitmapImage(Bitmap bitmap) 153 | { 154 | using MemoryStream stream = new(); 155 | bitmap.Save(stream, ImageFormat.Jpeg); 156 | 157 | stream.Position = 0; 158 | BitmapImage result = new(); 159 | result.BeginInit(); 160 | result.CacheOption = BitmapCacheOption.OnLoad; 161 | result.StreamSource = stream; 162 | result.EndInit(); 163 | result.Freeze(); 164 | return result; 165 | } 166 | 167 | public static BitmapImage BitmapToPngBitmapImage(Bitmap bitmap) 168 | { 169 | using MemoryStream stream = new(); 170 | bitmap.Save(stream, ImageFormat.Png); 171 | 172 | stream.Position = 0; 173 | BitmapImage result = new(); 174 | result.BeginInit(); 175 | result.CacheOption = BitmapCacheOption.OnLoad; 176 | result.StreamSource = stream; 177 | result.EndInit(); 178 | result.Freeze(); 179 | return result; 180 | } 181 | 182 | public static byte[] BitmapImageToByte(BitmapImage bmp) 183 | { 184 | JpegBitmapEncoder encoder = new(); 185 | encoder.Frames.Add(BitmapFrame.Create(bmp)); 186 | using MemoryStream stream = new(); 187 | encoder.Save(stream); 188 | return stream.ToArray(); 189 | } 190 | 191 | public static BitmapImage ByteToBitmapImage(byte[] bytes) 192 | { 193 | BitmapImage bitmapImage = new(); 194 | bitmapImage.BeginInit(); 195 | bitmapImage.CacheOption = BitmapCacheOption.OnLoad; 196 | bitmapImage.StreamSource = new MemoryStream(bytes); 197 | bitmapImage.EndInit(); 198 | if (bitmapImage.CanFreeze) 199 | { 200 | bitmapImage.Freeze(); 201 | } 202 | return bitmapImage; 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /Shared/Helpers/SQLiteHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Data.Common; 3 | using System.Data.SQLite; 4 | 5 | namespace Shared.Helpers 6 | { 7 | public class SQLiteHelper 8 | { 9 | protected string DataSource = string.Empty; 10 | 11 | protected SQLiteHelper(string filename) 12 | { 13 | DataSource = @".\database\" + filename; 14 | } 15 | 16 | protected SQLiteConnection GetSQLiteConnection() 17 | { 18 | string connStr = string.Format("Data Source={0}", DataSource); 19 | var con = new SQLiteConnection(connStr); 20 | return con; 21 | } 22 | 23 | protected static void PrepareCommand(SQLiteCommand cmd, SQLiteConnection conn, string sqlStr, params SQLiteParameter[]? p) 24 | { 25 | if (conn.State != ConnectionState.Open) 26 | { 27 | conn.Open(); 28 | } 29 | cmd.Parameters.Clear(); 30 | cmd.Connection = conn; 31 | cmd.CommandText = sqlStr; 32 | cmd.CommandType = CommandType.Text; 33 | cmd.CommandTimeout = 30; 34 | if (p != null && p.Length >= 1) 35 | { 36 | foreach (SQLiteParameter parm in p) 37 | { 38 | cmd.Parameters.AddWithValue(parm.ParameterName, parm.Value); 39 | } 40 | } 41 | } 42 | 43 | protected static async Task PrepareCommandAsync(SQLiteCommand cmd, SQLiteConnection conn, string sqlStr, params SQLiteParameter[]? p) 44 | { 45 | if (conn.State != ConnectionState.Open) 46 | { 47 | await conn.OpenAsync(); 48 | } 49 | cmd.Parameters.Clear(); 50 | cmd.Connection = conn; 51 | cmd.CommandText = sqlStr; 52 | cmd.CommandType = CommandType.Text; 53 | cmd.CommandTimeout = 30; 54 | if (p != null && p.Length >= 1) 55 | { 56 | foreach (SQLiteParameter parm in p) 57 | { 58 | cmd.Parameters.AddWithValue(parm.ParameterName, parm.Value); 59 | } 60 | } 61 | } 62 | 63 | protected DataTable ExecuteDataTable(string cmdText, params SQLiteParameter[]? data) 64 | { 65 | var dt = new DataTable(); 66 | using (SQLiteConnection connection = GetSQLiteConnection()) 67 | { 68 | var command = new SQLiteCommand(); 69 | PrepareCommand(command, connection, cmdText, data); 70 | SQLiteDataReader reader = command.ExecuteReader(); 71 | dt.Load(reader); 72 | } 73 | return dt; 74 | } 75 | 76 | protected async ValueTask ExecuteDataTableAsync(string cmdText, params SQLiteParameter[]? data) 77 | { 78 | var dt = new DataTable(); 79 | using (SQLiteConnection connection = GetSQLiteConnection()) 80 | { 81 | var command = new SQLiteCommand(); 82 | await PrepareCommandAsync(command, connection, cmdText, data); 83 | DbDataReader reader = await command.ExecuteReaderAsync(); 84 | dt.Load(reader); 85 | } 86 | return dt; 87 | } 88 | 89 | protected int ExecuteNonQuery(string cmdText, params SQLiteParameter[]? data) 90 | { 91 | using SQLiteConnection connection = GetSQLiteConnection(); 92 | var command = new SQLiteCommand(); 93 | PrepareCommand(command, connection, cmdText, data); 94 | return command.ExecuteNonQuery(); 95 | } 96 | 97 | protected async ValueTask ExecuteNonQueryAsync(string cmdText, params SQLiteParameter[]? data) 98 | { 99 | using SQLiteConnection connection = GetSQLiteConnection(); 100 | SQLiteCommand command = new(); 101 | await PrepareCommandAsync(command, connection, cmdText, data); 102 | return await command.ExecuteNonQueryAsync(); 103 | } 104 | 105 | protected SQLiteDataReader ExecuteReader(string cmdText, params SQLiteParameter[]? data) 106 | { 107 | var command = new SQLiteCommand(); 108 | using SQLiteConnection connection = GetSQLiteConnection(); 109 | PrepareCommand(command, connection, cmdText, data); 110 | SQLiteDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection); 111 | return reader; 112 | } 113 | 114 | protected async ValueTask ExecuteReaderAsync(string cmdText, params SQLiteParameter[]? data) 115 | { 116 | var command = new SQLiteCommand(); 117 | using SQLiteConnection connection = GetSQLiteConnection(); 118 | await PrepareCommandAsync(command, connection, cmdText, data); 119 | DbDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.CloseConnection); 120 | return reader; 121 | } 122 | 123 | protected object ExecuteScalar(string cmdText, params SQLiteParameter[]? data) 124 | { 125 | using SQLiteConnection connection = GetSQLiteConnection(); 126 | var cmd = new SQLiteCommand(); 127 | PrepareCommand(cmd, connection, cmdText, data); 128 | return cmd.ExecuteScalar(); 129 | } 130 | 131 | protected async ValueTask ExecuteScalarAsync(string cmdText, params SQLiteParameter[]? data) 132 | { 133 | using SQLiteConnection connection = GetSQLiteConnection(); 134 | var cmd = new SQLiteCommand(); 135 | await PrepareCommandAsync(cmd, connection, cmdText, data); 136 | return await cmd.ExecuteScalarAsync(); 137 | } 138 | 139 | protected async void ResetDataBassAsync() 140 | { 141 | using SQLiteConnection conn = GetSQLiteConnection(); 142 | var cmd = new SQLiteCommand(); 143 | 144 | if (conn.State != ConnectionState.Open) 145 | { 146 | await conn.OpenAsync(); 147 | } 148 | cmd.Parameters.Clear(); 149 | cmd.Connection = conn; 150 | cmd.CommandText = "vacuum"; 151 | cmd.CommandType = CommandType.Text; 152 | cmd.CommandTimeout = 30; 153 | await cmd.ExecuteNonQueryAsync(); 154 | } 155 | 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Shared/Helpers/Settings.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | namespace Shared.Helpers 4 | { 5 | public static class SettingsHelper 6 | { 7 | private static readonly Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); 8 | 9 | //设置键值 10 | public static void SetConfig(string key, string value) 11 | { 12 | config.AppSettings.Settings[key].Value = value; 13 | config.Save(ConfigurationSaveMode.Minimal); 14 | ConfigurationManager.RefreshSection("appSettings"); 15 | } 16 | 17 | //获取键值 18 | public static string GetConfig(string key) 19 | { 20 | return config.AppSettings.Settings[key].Value; 21 | } 22 | 23 | public static bool GetBoolean(string key) 24 | { 25 | return Boolean.Parse(GetConfig(key)); 26 | } 27 | 28 | public static int GetInt(string key) 29 | { 30 | return Int32.Parse(GetConfig(key)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Shared/Helpers/UsersDb.cs: -------------------------------------------------------------------------------- 1 | using Shared.Models; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Data.SQLite; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace Shared.Helpers 9 | { 10 | public class UsersDb : SQLiteHelper 11 | { 12 | private static readonly Dictionary DataBaceList = []; 13 | 14 | private UsersDb(string filename) : base(filename) 15 | { 16 | DataSource = @".\database\" + filename; 17 | } 18 | 19 | public static UsersDb GetDatabase(string filename) 20 | { 21 | if (DataBaceList.TryGetValue(filename, out UsersDb? value)) 22 | { 23 | return value; 24 | } 25 | else 26 | { 27 | UsersDb db = new(filename); 28 | if (File.Exists(@".\database\" + filename)) 29 | { 30 | DataBaceList.Add(filename, db); 31 | } 32 | return db; 33 | } 34 | } 35 | 36 | public static bool IsDatabaseConnected(string filename) 37 | { 38 | if (DataBaceList.ContainsKey(filename)) 39 | { 40 | return true; 41 | } 42 | else 43 | { 44 | return false; 45 | } 46 | } 47 | 48 | public async Task CreateDataBaseAsync() 49 | { 50 | string? path = Path.GetDirectoryName(DataSource); 51 | if ((!string.IsNullOrWhiteSpace(path)) && (!Directory.Exists(path))) 52 | { 53 | Directory.CreateDirectory(path); 54 | } 55 | if (!File.Exists(DataSource)) 56 | { 57 | SQLiteConnection.CreateFile(DataSource); 58 | StringBuilder sbr = new(); 59 | sbr.AppendLine("CREATE TABLE IF NOT EXISTS 'main' ("); 60 | sbr.AppendLine("'uid' TEXT PRIMARY KEY NOT NULL,"); 61 | sbr.AppendLine("'name' TEXT NOT NULL,"); 62 | sbr.AppendLine("'sex' TEXT,"); 63 | sbr.AppendLine("'age' TEXT,"); 64 | sbr.AppendLine("'joinTime' TEXT,"); 65 | sbr.AppendLine("'feature' TEXT NOT NULL,"); 66 | sbr.AppendLine("'image' BLOB NOT NULL"); 67 | sbr.AppendLine(");"); 68 | await ExecuteNonQueryAsync(sbr.ToString()); 69 | } 70 | } 71 | 72 | public async ValueTask ExecutePagerSimpleAsync(int pageIndex, int pageSize) 73 | { 74 | StringBuilder sbr = new(); 75 | sbr.AppendLine("SELECT uid, name, sex, age, joinTime FROM main LIMIT "); 76 | sbr.AppendLine(pageSize.ToString()); 77 | sbr.AppendLine(" OFFSET "); 78 | int OffsetIndex = (pageIndex - 1) * pageSize; 79 | sbr.AppendLine(OffsetIndex.ToString()); 80 | return await ExecuteDataTableAsync(sbr.ToString()); 81 | } 82 | 83 | public int GetRecordCount() 84 | { 85 | object result = ExecuteScalar("SELECT count(uid) FROM main"); 86 | return Convert.ToInt32(result); 87 | } 88 | 89 | public async ValueTask GetRecordCountAsync() 90 | { 91 | object? result = await ExecuteScalarAsync("SELECT count(uid) FROM main"); 92 | return Convert.ToInt32(result); 93 | } 94 | 95 | public async ValueTask ExistsAsync(string uid) 96 | { 97 | if (string.IsNullOrEmpty(uid)) return false; 98 | object? result = await ExecuteScalarAsync($"SELECT COUNT(uid) FROM main WHERE uid = '{uid}'"); 99 | if (Convert.ToInt32(result) > 0) 100 | { 101 | return true; 102 | } 103 | else 104 | { 105 | return false; 106 | } 107 | } 108 | 109 | public async ValueTask GetOneUserAsync(string uid) 110 | { 111 | string sql = $"SELECT * FROM main WHERE uid = '{uid}'"; 112 | 113 | using SQLiteConnection DbConnection = GetSQLiteConnection(); 114 | if (DbConnection.State != ConnectionState.Open) 115 | { 116 | await DbConnection.OpenAsync(); 117 | } 118 | using SQLiteCommand command = new(sql, DbConnection); 119 | using DbDataReader reader = await command.ExecuteReaderAsync(); 120 | reader.Read(); 121 | User user = new(reader.GetString(0), reader.GetString(1), reader.GetString(2), reader.GetString(3), reader.GetString(4), reader.GetString(5), ImageProcess.ByteToBitmapImage((byte[])reader.GetValue(6))); 122 | return user; 123 | } 124 | 125 | public string GetOneFaceFeatureStringByIndex(int index) 126 | { 127 | return (string)ExecuteScalar($"SELECT feature FROM main ORDER BY uid DESC LIMIT 1 OFFSET {index}"); 128 | } 129 | 130 | public string GetOneNameByIndex(int index) 131 | { 132 | return (string)ExecuteScalar($"SELECT name FROM main ORDER BY uid DESC LIMIT 1 OFFSET {index}"); 133 | } 134 | 135 | public void AddUserAsync(User user) 136 | { 137 | string sqlStr = $"INSERT INTO main VALUES ({user.Uid},'{user.Name}','{user.Sex}','{user.Age}','{user.JoinTime}','{user.Feature}',@faceImage)"; 138 | byte[] image = ImageProcess.BitmapImageToByte(user.FaceImage); 139 | SQLiteParameter parameter = new("@faceImage", DbType.Binary, image.Length) 140 | { 141 | Value = image 142 | }; 143 | _ = ExecuteNonQueryAsync(sqlStr, parameter); 144 | } 145 | 146 | public void DelFaceAsync(string uid) 147 | { 148 | _ = ExecuteNonQueryAsync($"DELETE FROM main WHERE uid = '{uid}'"); 149 | } 150 | 151 | public void UpdateFaceAsync(User user) 152 | { 153 | string sqlStr = $"UPDATE main SET name = '{user.Name}', sex = '{user.Sex}', age = '{user.Age}', joinTime = '{user.JoinTime}', image = @faceImage WHERE uid = '{user.Uid}'"; 154 | byte[] image = ImageProcess.BitmapImageToByte(user.FaceImage); 155 | SQLiteParameter parameter = new("@faceImage", DbType.Binary, image.Length) 156 | { 157 | Value = image 158 | }; 159 | _ = ExecuteNonQueryAsync(sqlStr, parameter); 160 | } 161 | 162 | public async void UpdateSimpleAsync(string uid, string name, string? sex, string? age, string? joinTime) 163 | { 164 | string sql = $"UPDATE main SET name = '{name}', sex = '{sex}', age = '{age}', joinTime = '{joinTime}' WHERE uid = '{uid}'"; 165 | await ExecuteNonQueryAsync(sql); 166 | } 167 | 168 | public async ValueTask AutoSuggestByNameAsync(string str) 169 | { 170 | string sql = $"SELECT uid,name,sex,age,joinTime FROM main WHERE name LIKE '%{str}%'"; 171 | return await ExecuteDataTableAsync(sql); 172 | } 173 | 174 | public async ValueTask MergeDatabaseAsync(string newDbPath) 175 | { 176 | int mergedCount = 0; 177 | int repeatedCount = 0; 178 | 179 | using SQLiteConnection oldDbConnection = GetSQLiteConnection(); 180 | if (oldDbConnection.State != ConnectionState.Open) 181 | { 182 | await oldDbConnection.OpenAsync(); 183 | } 184 | 185 | using SQLiteConnection newDbConnection = new() 186 | { 187 | ConnectionString = "Data Source=" + newDbPath 188 | }; 189 | await newDbConnection.OpenAsync(); 190 | 191 | SQLiteCommand selectCommand = new("SELECT * FROM main", newDbConnection); 192 | DbDataReader reader = await selectCommand.ExecuteReaderAsync(); 193 | 194 | SQLiteCommand command = new(); 195 | SQLiteTransaction transaction = oldDbConnection.BeginTransaction(); 196 | 197 | while (reader.Read()) 198 | { 199 | string uid = reader.GetString(0); 200 | if (!await ExistsAsync(uid)) 201 | { 202 | string sqlStr = $"INSERT INTO main VALUES ('{uid}', '{reader.GetString(1)}','{reader.GetString(2)}','{reader.GetString(3)}','{reader.GetString(4)}','{reader.GetString(5)}', @faceImage)"; 203 | byte[] image = (byte[])reader.GetValue(6); 204 | SQLiteParameter parameter = new("@faceImage", DbType.Binary, image.Length) { Value = image }; 205 | await PrepareCommandAsync(command, oldDbConnection, sqlStr, parameter); 206 | command.Transaction = transaction; 207 | await command.ExecuteNonQueryAsync(); 208 | mergedCount++; 209 | } 210 | else 211 | { 212 | repeatedCount++; 213 | } 214 | } 215 | transaction.Commit(); 216 | command.Dispose(); 217 | reader.Dispose(); 218 | return [mergedCount, repeatedCount]; 219 | } 220 | 221 | public async void CleanDatabaseAsync() 222 | { 223 | using SQLiteConnection conn = GetSQLiteConnection(); 224 | var cmd = new SQLiteCommand(); 225 | 226 | if (conn.State != ConnectionState.Open) 227 | { 228 | await conn.OpenAsync(); 229 | } 230 | cmd.Parameters.Clear(); 231 | cmd.Connection = conn; 232 | cmd.CommandText = "DELETE FROM main;"; 233 | cmd.CommandType = CommandType.Text; 234 | cmd.CommandTimeout = 30; 235 | await cmd.ExecuteNonQueryAsync(); 236 | 237 | cmd.Parameters.Clear(); 238 | cmd.CommandText = "vacuum"; 239 | await cmd.ExecuteNonQueryAsync(); 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /Shared/Helpers/Utils.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Media; 3 | using Wpf.Ui.Appearance; 4 | using Wpf.Ui.Controls; 5 | 6 | namespace Shared.Helpers 7 | { 8 | public static class Utils 9 | { 10 | private static readonly Version _osVersion = Environment.OSVersion.Version; 11 | public static bool IsOSWindows11Insider1OrNewer => _osVersion.Build >= 22523; 12 | 13 | public static int GetCurrentApplicationThemeIndex(string theme) 14 | { 15 | if (theme == "System") 16 | { 17 | return 0; 18 | } 19 | else if (theme == "Light") 20 | { 21 | return 1; 22 | } 23 | else 24 | { 25 | return 2; 26 | } 27 | } 28 | 29 | public static ApplicationTheme GetUserApplicationTheme(string theme) 30 | { 31 | if (theme == "System") 32 | { 33 | SystemTheme systemTheme = ApplicationThemeManager.GetSystemTheme(); 34 | 35 | if (systemTheme is SystemTheme.Dark or SystemTheme.CapturedMotion or SystemTheme.Glow) 36 | { 37 | return ApplicationTheme.Dark; 38 | } 39 | else 40 | { 41 | return ApplicationTheme.Light; 42 | } 43 | } 44 | else if (theme == "Light") 45 | { 46 | return ApplicationTheme.Light; 47 | } 48 | else 49 | { 50 | return ApplicationTheme.Dark; 51 | } 52 | } 53 | 54 | public static int GetCurrentBackdropIndex(string backdrop) 55 | { 56 | if (backdrop == "None") 57 | { 58 | return 0; 59 | } 60 | else if (backdrop == "Acrylic") 61 | { 62 | return 1; 63 | } 64 | else if (backdrop == "Mica") 65 | { 66 | return 2; 67 | } 68 | else 69 | { 70 | return 3; 71 | } 72 | } 73 | 74 | public static WindowBackdropType GetUserBackdrop(string backdrop) 75 | { 76 | if (backdrop == "None") 77 | { 78 | return WindowBackdropType.None; 79 | } 80 | else if (backdrop == "Acrylic") 81 | { 82 | return WindowBackdropType.Acrylic; 83 | } 84 | else if (backdrop == "Mica") 85 | { 86 | return WindowBackdropType.Mica; 87 | } 88 | else 89 | { 90 | return WindowBackdropType.Tabbed; 91 | } 92 | } 93 | 94 | public static SolidColorBrush StringToSolidColorBrush(string color) 95 | { 96 | return new SolidColorBrush((Color)ColorConverter.ConvertFromString(color)); 97 | } 98 | 99 | public static SolidColorBrush ColorToSolidColorBrush(Color color) 100 | { 101 | return new SolidColorBrush(color); 102 | } 103 | 104 | public static Color StringToColor(string color) 105 | { 106 | return (Color)ColorConverter.ConvertFromString(color); 107 | } 108 | 109 | public static T? FindVisualChild(DependencyObject? obj) where T : DependencyObject 110 | { 111 | if (obj != null) 112 | { 113 | for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) 114 | { 115 | DependencyObject child = VisualTreeHelper.GetChild(obj, i); 116 | if (child != null && child is T t) 117 | { 118 | return t; 119 | } 120 | T? childItem = FindVisualChild(child); 121 | if (childItem != null) 122 | { 123 | return childItem; 124 | } 125 | } 126 | } 127 | return null; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Shared/Helpers/Win32Helper.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Text; 3 | 4 | namespace Shared.Helpers 5 | { 6 | public partial class Win32Helper 7 | { 8 | // 声明常量 9 | public const int WM_COPYDATA = 0x004A; 10 | 11 | // 定义 COPYDATASTRUCT 结构 12 | [StructLayout(LayoutKind.Sequential)] 13 | public struct COPYDATASTRUCT 14 | { 15 | public IntPtr dwData; 16 | public int cbData; 17 | public IntPtr lpData; 18 | } 19 | 20 | /// 21 | /// 发送消息 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | /// 28 | [DllImport("user32.dll")] 29 | public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref COPYDATASTRUCT lParam); 30 | 31 | /// 32 | /// 发送字符串消息 33 | /// 34 | /// 35 | /// 36 | public static void SendMessageString(IntPtr hWnd, string message) 37 | { 38 | if (string.IsNullOrEmpty(message)) return; 39 | 40 | byte[] messageBytes = Encoding.Unicode.GetBytes(message + '\0'); // 添加终止符 41 | 42 | COPYDATASTRUCT cds = new() 43 | { 44 | dwData = IntPtr.Zero, 45 | cbData = messageBytes.Length 46 | }; 47 | cds.lpData = Marshal.AllocHGlobal(cds.cbData); 48 | Marshal.Copy(messageBytes, 0, cds.lpData, cds.cbData); 49 | try 50 | { 51 | SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, ref cds); 52 | } 53 | finally 54 | { 55 | //释放分配的内存,即使发生异常也不会泄漏资源 56 | Marshal.FreeHGlobal(cds.lpData); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Shared/Models/EncodingFace.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Windows.Media.Imaging; 3 | using ViewFaceCore.Model; 4 | 5 | namespace Shared.Models 6 | { 7 | public record class EncodingFace 8 | { 9 | public Bitmap FullImage { get; set; } 10 | public BitmapImage FaceImage { get; set; } 11 | public FaceInfo FaceInfo { get; set; } 12 | public FaceMarkPoint[] FaceMarkPoints { get; set; } 13 | 14 | public EncodingFace(Bitmap fullImage, BitmapImage faceImage, FaceInfo faceInfo, FaceMarkPoint[] faceMarkPoints) 15 | { 16 | FullImage = fullImage; 17 | FaceImage = faceImage; 18 | FaceInfo = faceInfo; 19 | FaceMarkPoints = faceMarkPoints; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Shared/Models/PageButton.cs: -------------------------------------------------------------------------------- 1 | namespace Shared.Models 2 | { 3 | public class PageButton 4 | { 5 | public string Name { get; init; } 6 | public bool IsCurrentPage { get; init; } 7 | public bool IsEnabled { get; init; } 8 | 9 | public PageButton(string name, bool isCurrentPage = false, bool isEnabled = true) 10 | { 11 | Name = name; 12 | IsCurrentPage = isCurrentPage; 13 | IsEnabled = isEnabled; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Shared/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Media.Imaging; 2 | 3 | namespace Shared.Models 4 | { 5 | public record class User 6 | { 7 | public string Uid { get; set; } 8 | public string Name { get; set; } 9 | public string? Sex { get; set; } 10 | public string? Age { get; set; } 11 | public string? JoinTime { get; set; } 12 | public string Feature { get; set; } 13 | public BitmapImage FaceImage { get; set; } 14 | 15 | public User(string uid, string name, string? sex, string? age, string? joinTime, string feature, BitmapImage faceImage) 16 | { 17 | Uid = uid; 18 | Name = name; 19 | Sex = sex; 20 | Age = age; 21 | JoinTime = joinTime; 22 | Feature = feature; 23 | FaceImage = faceImage; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Shared/Services/Contracts/IWindow.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace Shared.Services.Contracts 4 | { 5 | public interface IWindow 6 | { 7 | event RoutedEventHandler Loaded; 8 | void Show(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Shared/Services/WindowsProviderService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System.Windows; 3 | 4 | namespace Shared.Services 5 | { 6 | public class WindowsProviderService 7 | { 8 | private readonly IServiceProvider _serviceProvider; 9 | 10 | public WindowsProviderService(IServiceProvider serviceProvider) 11 | { 12 | _serviceProvider = serviceProvider; 13 | } 14 | 15 | public void Show() 16 | where T : class 17 | { 18 | if (!typeof(Window).IsAssignableFrom(typeof(T))) 19 | { 20 | throw new InvalidOperationException($"The window class should be derived from {typeof(Window)}."); 21 | } 22 | 23 | Window windowInstance = _serviceProvider.GetService() as Window ?? throw new InvalidOperationException("Window is not registered as service."); 24 | windowInstance.Owner = Application.Current.MainWindow; 25 | windowInstance.Show(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Shared/Shared.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0-windows10.0.26100.0 5 | 10.0.18362.0 6 | enable 7 | true 8 | enable 9 | x64 10 | ../bin 11 | 12 | 13 | 14 | Smart library共享资源库 15 | Copyright © 2025 赵杨磊.All Rights Reserved. 16 | 1.0.0.25122 17 | 1.0.0.25122 18 | true 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Shared/Style/MyCheckBox.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 16 | 17 | #FFFF0000 18 | 19 | 11,5,11,6 20 | 1 21 | 8,0,0,0 22 | 14 23 | 22 24 | 22 25 | 26 | 148 | 149 | -------------------------------------------------------------------------------- /Shared/Style/SelectableTextBlock.xaml: -------------------------------------------------------------------------------- 1 |  2 | 34 | -------------------------------------------------------------------------------- /Shared/branding/borrowAttitude.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/branding/borrowAttitude.psd -------------------------------------------------------------------------------- /Shared/branding/cameraEmpty.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/branding/cameraEmpty.psd -------------------------------------------------------------------------------- /SmartLibrary.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34511.84 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartLibrary", "SmartLibrary\SmartLibrary.csproj", "{FB308821-2A80-4258-BD6F-916DB1FB8E1A}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{1B5E8380-4839-4DC8-83BE-BC2D40C3CCAF}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {FB308821-2A80-4258-BD6F-916DB1FB8E1A}.Debug|x64.ActiveCfg = Debug|x64 17 | {FB308821-2A80-4258-BD6F-916DB1FB8E1A}.Debug|x64.Build.0 = Debug|x64 18 | {FB308821-2A80-4258-BD6F-916DB1FB8E1A}.Release|x64.ActiveCfg = Release|x64 19 | {FB308821-2A80-4258-BD6F-916DB1FB8E1A}.Release|x64.Build.0 = Release|x64 20 | {1B5E8380-4839-4DC8-83BE-BC2D40C3CCAF}.Debug|x64.ActiveCfg = Debug|x64 21 | {1B5E8380-4839-4DC8-83BE-BC2D40C3CCAF}.Debug|x64.Build.0 = Debug|x64 22 | {1B5E8380-4839-4DC8-83BE-BC2D40C3CCAF}.Release|x64.ActiveCfg = Release|x64 23 | {1B5E8380-4839-4DC8-83BE-BC2D40C3CCAF}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {0F1DEB0D-4D26-45D3-8757-AFFEB4E36AA0} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /SmartLibrary/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SmartLibrary/App.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /SmartLibrary/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Shared.Helpers; 5 | using Shared.Services; 6 | using Shared.Services.Contracts; 7 | using SmartLibrary.Services; 8 | using System.Diagnostics; 9 | using System.Security.Cryptography; 10 | using System.Windows.Threading; 11 | using Wpf.Ui; 12 | using Wpf.Ui.DependencyInjection; 13 | using Yitter.IdGenerator; 14 | namespace SmartLibrary 15 | { 16 | public partial class App : Application 17 | { 18 | private Mutex? mutex; 19 | 20 | private static readonly IHost _host = Host.CreateDefaultBuilder() 21 | .ConfigureAppConfiguration(c => 22 | { 23 | c.SetBasePath(AppContext.BaseDirectory); 24 | }) 25 | .ConfigureServices( 26 | (context, services) => 27 | { 28 | services.AddNavigationViewPageProvider(); 29 | 30 | // App Host 31 | services.AddHostedService(); 32 | 33 | // Main window container with navigation 34 | services.AddSingleton(); 35 | services.AddSingleton(); 36 | services.AddSingleton(); 37 | services.AddSingleton(); 38 | services.AddSingleton(); 39 | services.AddSingleton(); 40 | 41 | // Views and ViewModels 42 | services.AddSingleton(); 43 | services.AddSingleton(); 44 | services.AddSingleton(); 45 | services.AddSingleton(); 46 | services.AddSingleton(); 47 | services.AddSingleton(); 48 | services.AddSingleton(); 49 | services.AddSingleton(); 50 | services.AddSingleton(); 51 | services.AddSingleton(); 52 | services.AddSingleton(); 53 | services.AddSingleton(); 54 | 55 | services.AddTransient(); 56 | services.AddTransient(); 57 | services.AddTransient(); 58 | services.AddTransient(); 59 | services.AddTransient(); 60 | services.AddTransient(); 61 | services.AddTransient(); 62 | services.AddTransient(); 63 | } 64 | ).Build(); 65 | 66 | public static T? GetService() 67 | where T : class 68 | { 69 | return _host.Services.GetService(typeof(T)) as T; 70 | } 71 | 72 | public static T GetRequiredService() 73 | where T : class 74 | { 75 | return _host.Services.GetRequiredService(); 76 | } 77 | 78 | private void OnStartup(object sender, StartupEventArgs e) 79 | { 80 | mutex = new Mutex(true, "SmartLibrary", out bool aIsNewInstance); 81 | if (aIsNewInstance) 82 | { 83 | //IdGenerator全局初始化 84 | YitIdHelper.SetIdGenerator(new((ushort)RandomNumberGenerator.GetInt32(60))); 85 | _host.Start(); 86 | mutex.ReleaseMutex(); 87 | } 88 | else 89 | { 90 | Process current = Process.GetCurrentProcess(); 91 | foreach (Process process in Process.GetProcessesByName(current.ProcessName)) 92 | { 93 | if (process.Id != current.Id) 94 | { 95 | Win32Helper.SendMessageString(process.MainWindowHandle, "SmartLibrary"); 96 | break; 97 | } 98 | } 99 | Current.Shutdown(); 100 | } 101 | } 102 | 103 | private void OnExit(object sender, ExitEventArgs e) 104 | { 105 | _host.StopAsync().Wait(); 106 | _host.Dispose(); 107 | } 108 | 109 | private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) 110 | { 111 | MessageBox.Show("我们很抱歉,当前应用程序遇到一些问题...\n " + e.Exception.Message); 112 | e.Handled = true; 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /SmartLibrary/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: ThemeInfo( 2 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 3 | //(used if a resource is not found in the page, 4 | // or application resource dictionaries) 5 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 6 | //(used if a resource is not found in the page, 7 | // app, or any theme specific resource dictionaries) 8 | )] 9 | -------------------------------------------------------------------------------- /SmartLibrary/Assets/DynamicPic/dark/LoadingPictureError.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/DynamicPic/dark/LoadingPictureError.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/DynamicPic/dark/PictureEmpty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/DynamicPic/dark/PictureEmpty.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/DynamicPic/dark/cameraEmpty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/DynamicPic/dark/cameraEmpty.jpg -------------------------------------------------------------------------------- /SmartLibrary/Assets/DynamicPic/dark/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/DynamicPic/dark/cross.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/DynamicPic/dark/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/DynamicPic/dark/tick.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/DynamicPic/light/LoadingPictureError.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/DynamicPic/light/LoadingPictureError.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/DynamicPic/light/PictureEmpty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/DynamicPic/light/PictureEmpty.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/DynamicPic/light/cameraEmpty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/DynamicPic/light/cameraEmpty.jpg -------------------------------------------------------------------------------- /SmartLibrary/Assets/DynamicPic/light/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/DynamicPic/light/cross.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/DynamicPic/light/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/DynamicPic/light/tick.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/applicationIcon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/applicationIcon-512.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/background.jpg -------------------------------------------------------------------------------- /SmartLibrary/Assets/bluetooth-connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/bluetooth-connected.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/bluetooth-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/bluetooth-disabled.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/bluetooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/bluetooth.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/bookinfo-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/bookinfo-512x512.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/borrowbook-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/borrowbook-512x512.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/database.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/findbook-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/findbook-512x512.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/homeBluetooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/homeBluetooth.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/loading.gif -------------------------------------------------------------------------------- /SmartLibrary/Assets/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/right.png -------------------------------------------------------------------------------- /SmartLibrary/Assets/wrong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/Assets/wrong.png -------------------------------------------------------------------------------- /SmartLibrary/Converters/BookExistedConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace SmartLibrary.Converters 5 | { 6 | internal sealed class BookExistedConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 9 | { 10 | if ((bool)value) 11 | { 12 | return "编辑"; 13 | } 14 | else 15 | { 16 | return "添加"; 17 | } 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /SmartLibrary/Converters/BoolToBorrowOrReturnTextConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace SmartLibrary.Converters 5 | { 6 | internal sealed class BoolToBorrowOrReturnTextConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 9 | { 10 | if ((bool)value) 11 | { 12 | return "还书"; 13 | } 14 | else 15 | { 16 | return "借阅"; 17 | } 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SmartLibrary/Converters/BoolToImageSourceConverter.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.Helpers; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using System.Windows.Media.Imaging; 5 | 6 | namespace SmartLibrary.Converters 7 | { 8 | internal sealed class BoolToImageSourceConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | if ((bool)value) 13 | { 14 | return new BitmapImage(new Uri($"pack://application:,,,/Assets/DynamicPic/{ResourceManager.CurrentTheme}/cross.png", UriKind.Absolute)); 15 | } 16 | else 17 | { 18 | return new BitmapImage(new Uri($"pack://application:,,,/Assets/DynamicPic/{ResourceManager.CurrentTheme}/tick.png", UriKind.Absolute)); 19 | } 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /SmartLibrary/Converters/BoolToOpenCameraTextConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace SmartLibrary.Converters 5 | { 6 | public sealed class BoolToOpenCameraTextConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 9 | { 10 | if ((bool)value) 11 | { 12 | return "关闭摄像头"; 13 | } 14 | else 15 | { 16 | return "打开摄像头"; 17 | } 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SmartLibrary/Extensions/ImageDecoder.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.Helpers; 2 | using System.Windows.Controls; 3 | using System.Windows.Media.Animation; 4 | using System.Windows.Media.Imaging; 5 | using Wpf.Ui.Controls; 6 | using Image = Wpf.Ui.Controls.Image; 7 | 8 | namespace SmartLibrary.Extensions 9 | { 10 | public partial class ImageDecoder 11 | { 12 | #region Isbn Property 13 | public static readonly DependencyProperty IsbnProperty = DependencyProperty.RegisterAttached("Isbn", typeof(string), typeof(ImageDecoder)); 14 | 15 | public static string GetIsbn(Image image) 16 | { 17 | return (string)image.GetValue(IsbnProperty); 18 | } 19 | 20 | public static void SetIsbn(Image image, string value) 21 | { 22 | image.SetValue(IsbnProperty, value); 23 | } 24 | #endregion Isbn Property 25 | 26 | #region Source Property 27 | public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("Source", typeof(string), typeof(ImageDecoder), new PropertyMetadata(new PropertyChangedCallback(OnSourceChanged))); 28 | 29 | public static string GetSource(Image image) 30 | { 31 | return (string)image.GetValue(SourceProperty); 32 | } 33 | 34 | public static void SetSource(Image image, string value) 35 | { 36 | image.SetValue(SourceProperty, value); 37 | } 38 | #endregion Source Property 39 | 40 | static ImageDecoder() 41 | { 42 | ImageQueue.OnComplate += ImageQueue_OnComplate; 43 | } 44 | 45 | private static void ImageQueue_OnComplate(Image i, BitmapImage b) 46 | { 47 | i.Source = b; 48 | Storyboard storyboard = new(); 49 | DoubleAnimation doubleAnimation = new(0.0, 1.0, new Duration(TimeSpan.FromMilliseconds(500.0))); 50 | Storyboard.SetTarget(doubleAnimation, i); 51 | Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("Opacity", [])); 52 | storyboard.Children.Add(doubleAnimation); 53 | storyboard.Begin(); 54 | 55 | if (i.Parent is Grid grid) 56 | { 57 | foreach (object c in grid.Children) 58 | { 59 | if (c is ProgressRing progressring) 60 | { 61 | progressring.IsIndeterminate = false; 62 | progressring.Visibility = Visibility.Collapsed; 63 | return; 64 | } 65 | } 66 | } 67 | } 68 | 69 | private static void OnSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) 70 | { 71 | ImageQueue.Queue((Image)o, (string)o.GetValue(IsbnProperty), (string)e.NewValue); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /SmartLibrary/Helpers/ImageQueue.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.Models; 2 | using System.IO; 3 | using System.Windows.Media.Imaging; 4 | using Wpf.Ui.Controls; 5 | 6 | namespace SmartLibrary.Helpers 7 | { 8 | public static class ImageQueue 9 | { 10 | private static readonly AutoResetEvent autoEvent = new(false); 11 | private static readonly Queue Stacks = new(); 12 | 13 | private static readonly LocalStorage _storage = new(); 14 | private static int downloadingCount = 0; 15 | 16 | public delegate void ComplateDelegate(Image image, BitmapImage bitmap); 17 | public static event ComplateDelegate OnComplate = delegate { }; 18 | 19 | static ImageQueue() 20 | { 21 | _storage.BookShelfPictureLoadigCompleted += LoadingCompleted; 22 | Thread thread = new(new ThreadStart(DownloadImage)) 23 | { 24 | Name = "下载图片", 25 | IsBackground = true 26 | }; 27 | thread.Start(); 28 | } 29 | 30 | private static void LoadingCompleted(ImageQueueInfo imageInfo) 31 | { 32 | BitmapImage? bitmapImage = null; 33 | if (imageInfo.Url.StartsWith("pack")) 34 | { 35 | bitmapImage = new(new Uri(imageInfo.Url)); 36 | } 37 | else 38 | { 39 | using BinaryReader reader = new(File.Open(imageInfo.Url, FileMode.Open)); 40 | FileInfo fi = new(imageInfo.Url); 41 | byte[] bytes = reader.ReadBytes((int)fi.Length); 42 | reader.Close(); 43 | 44 | bitmapImage = new() 45 | { 46 | CacheOption = BitmapCacheOption.OnLoad 47 | }; 48 | bitmapImage.BeginInit(); 49 | bitmapImage.StreamSource = new MemoryStream(bytes); 50 | bitmapImage.EndInit(); 51 | } 52 | 53 | if (bitmapImage.CanFreeze) 54 | { 55 | bitmapImage.Freeze(); 56 | } 57 | 58 | imageInfo.Image.Dispatcher.BeginInvoke(new Action((image, bitmap) => 59 | { 60 | OnComplate(image.Image, bitmap); 61 | }), [imageInfo, bitmapImage]); 62 | 63 | downloadingCount--; 64 | autoEvent.Set(); 65 | } 66 | 67 | private static void DownloadImage() 68 | { 69 | while (true) 70 | { 71 | lock (Stacks) 72 | { 73 | if (Stacks.Count > 0) 74 | { 75 | ImageQueueInfo? t = Stacks.Dequeue(); 76 | _storage.GetBookShelfPicture(t); 77 | downloadingCount++; 78 | } 79 | } 80 | if (Stacks.Count > 0 && downloadingCount < 6) 81 | { 82 | continue; 83 | } 84 | else 85 | { 86 | autoEvent.WaitOne(); 87 | } 88 | } 89 | } 90 | 91 | public static void Queue(Image img, string isbn, string url) 92 | { 93 | lock (Stacks) 94 | { 95 | Stacks.Enqueue(new ImageQueueInfo { Url = url, Isbn = isbn, Image = img }); 96 | autoEvent.Set(); 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /SmartLibrary/Helpers/LocalStorage.cs: -------------------------------------------------------------------------------- 1 | using Shared.Helpers; 2 | using SmartLibrary.Models; 3 | using System.Drawing; 4 | using System.Drawing.Imaging; 5 | using System.IO; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace SmartLibrary.Helpers 9 | { 10 | public sealed partial class LocalStorage 11 | { 12 | [GeneratedRegex(@"^https?:\/\/", RegexOptions.IgnoreCase, "zh-CN")] 13 | private static partial Regex UriRegex(); 14 | 15 | public delegate void LoadingCompletedEventHandler(string path); 16 | public event LoadingCompletedEventHandler LoadingCompleted = delegate { }; 17 | 18 | public delegate void BookShelfPictureLoadingCompletedEventHandler(ImageQueueInfo t); 19 | public event BookShelfPictureLoadingCompletedEventHandler BookShelfPictureLoadigCompleted = delegate { }; 20 | 21 | private static readonly Network network = Network.Instance; 22 | 23 | public async void SearchPicture(string isbn, string? path) 24 | { 25 | if (!string.IsNullOrEmpty(path)) 26 | { 27 | string localFilePath = @".\pictures\" + isbn + ".jpg"; 28 | string tempFilePath = @".\temp\" + isbn + ".jpg"; 29 | if (File.Exists(localFilePath)) 30 | { 31 | LoadingCompleted(Path.GetFullPath(localFilePath)); 32 | } 33 | else if (File.Exists(tempFilePath)) 34 | { 35 | LoadingCompleted(Path.GetFullPath(tempFilePath)); 36 | } 37 | else 38 | { 39 | if (await SavePictureAsync(tempFilePath, path)) 40 | { 41 | await Task.Delay(300); 42 | LoadingCompleted(Path.GetFullPath(tempFilePath)); 43 | } 44 | else 45 | { 46 | LoadingCompleted($"pack://application:,,,/Assets/DynamicPic/{ResourceManager.CurrentTheme}/LoadingPictureError.png"); 47 | } 48 | } 49 | } 50 | else 51 | { 52 | LoadingCompleted(ResourceManager.EmptyImage); 53 | } 54 | } 55 | 56 | private static async Task SavePictureAsync(string path, string url) 57 | { 58 | byte[] picture = await network.GetPicture(url); 59 | if (picture.Length > 0) 60 | { 61 | string localPath = Path.GetDirectoryName(path) ?? string.Empty; 62 | if (!Directory.Exists(localPath)) 63 | { 64 | Directory.CreateDirectory(localPath); 65 | } 66 | using MemoryStream stream = new(picture); 67 | using Image image = Image.FromStream(stream); 68 | Image a = ImageProcess.Resize(image, 270, 390); 69 | a.Save(path, ImageFormat.Jpeg); 70 | return true; 71 | } 72 | else 73 | { 74 | return false; 75 | } 76 | } 77 | 78 | public static string AddPicture(string isbn, string? path) 79 | { 80 | string localFilePath = @".\pictures\" + isbn + ".jpg"; 81 | string tempFilePath = @".\temp\" + isbn + ".jpg"; 82 | if (string.IsNullOrEmpty(path)) 83 | { 84 | if (File.Exists(localFilePath)) 85 | { 86 | File.Delete(localFilePath); 87 | } 88 | return string.Empty; 89 | } 90 | else 91 | { 92 | if (File.Exists(path)) 93 | { 94 | using Image image = Image.FromFile(path); 95 | Image a = ImageProcess.Resize(image, 270, 390); 96 | a.Save(path, ImageFormat.Jpeg); 97 | return localFilePath; 98 | } 99 | else if (File.Exists(tempFilePath)) 100 | { 101 | File.Move(tempFilePath, localFilePath); 102 | return path; 103 | } 104 | else 105 | { 106 | _ = SavePictureAsync(localFilePath, path); 107 | return path; 108 | } 109 | } 110 | } 111 | 112 | public async void GetPicture(string isbn, string? path) 113 | { 114 | if (string.IsNullOrEmpty(path)) 115 | { 116 | LoadingCompleted(ResourceManager.EmptyImage); 117 | } 118 | else 119 | { 120 | string localFilePath = @".\pictures\" + isbn + ".jpg"; 121 | if (File.Exists(localFilePath)) 122 | { 123 | LoadingCompleted(Path.GetFullPath(localFilePath)); 124 | } 125 | else 126 | { 127 | if (ValidImageURL(path, out _)) 128 | { 129 | if (await SavePictureAsync(localFilePath, path)) 130 | { 131 | await Task.Delay(300); 132 | LoadingCompleted(Path.GetFullPath(localFilePath)); 133 | } 134 | else 135 | { 136 | LoadingCompleted($"pack://application:,,,/Assets/DynamicPic/{ResourceManager.CurrentTheme}/LoadingPictureError.png"); 137 | } 138 | 139 | } 140 | else 141 | { 142 | LoadingCompleted($"pack://application:,,,/Assets/DynamicPic/{ResourceManager.CurrentTheme}/LoadingPictureError.png"); 143 | } 144 | } 145 | } 146 | } 147 | 148 | public async void GetBookShelfPicture(ImageQueueInfo imageInfo) 149 | { 150 | if (string.IsNullOrEmpty(imageInfo.Url)) 151 | { 152 | imageInfo.Url = ResourceManager.EmptyImage; 153 | BookShelfPictureLoadigCompleted(imageInfo); 154 | } 155 | else 156 | { 157 | string localFilePath = @".\pictures\" + imageInfo.Isbn + ".jpg"; 158 | if (File.Exists(localFilePath)) 159 | { 160 | imageInfo.Url = Path.GetFullPath(localFilePath); 161 | BookShelfPictureLoadigCompleted(imageInfo); 162 | } 163 | else 164 | { 165 | if (ValidImageURL(imageInfo.Url, out _)) 166 | { 167 | if (await SavePictureAsync(localFilePath, imageInfo.Url)) 168 | { 169 | await Task.Delay(300); 170 | imageInfo.Url = Path.GetFullPath(localFilePath); 171 | BookShelfPictureLoadigCompleted(imageInfo); 172 | } 173 | else 174 | { 175 | imageInfo.Url = $"pack://application:,,,/Assets/DynamicPic/{ResourceManager.CurrentTheme}/LoadingPictureError.png"; 176 | BookShelfPictureLoadigCompleted(imageInfo); 177 | } 178 | } 179 | else 180 | { 181 | imageInfo.Url = $"pack://application:,,,/Assets/DynamicPic/{ResourceManager.CurrentTheme}/LoadingPictureError.png"; 182 | BookShelfPictureLoadigCompleted(imageInfo); 183 | } 184 | } 185 | } 186 | } 187 | 188 | private static bool ValidImageURL(string s, out Uri? resultURI) 189 | { 190 | if (!UriRegex().IsMatch(s)) 191 | { 192 | s = "http://" + s; 193 | } 194 | if (Uri.TryCreate(s, UriKind.Absolute, out resultURI)) 195 | { 196 | if (resultURI.Scheme == Uri.UriSchemeHttp || resultURI.Scheme == Uri.UriSchemeHttps) 197 | { 198 | if (s.EndsWith("jpg") || s.EndsWith("png")) 199 | { 200 | return true; 201 | } 202 | else 203 | { 204 | return false; 205 | } 206 | } 207 | else 208 | { 209 | return false; 210 | } 211 | } 212 | else 213 | { 214 | return false; 215 | } 216 | } 217 | } 218 | } -------------------------------------------------------------------------------- /SmartLibrary/Helpers/Network.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Security.Authentication; 3 | 4 | namespace SmartLibrary.Helpers 5 | { 6 | public sealed partial class Network 7 | { 8 | private static Network? _instance; 9 | private static readonly HttpClientHandler clientHandler = new(); 10 | private static readonly HttpClient httpClient = new(clientHandler); 11 | 12 | public static Network Instance 13 | { 14 | get 15 | { 16 | _instance ??= new Network(); 17 | return _instance; 18 | } 19 | } 20 | 21 | public bool IsInternetConnected { private set; get; } = System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable(); 22 | 23 | private Network() 24 | { 25 | System.Net.NetworkInformation.NetworkChange.NetworkAvailabilityChanged += (_, e) => IsInternetConnected = e.IsAvailable; 26 | clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => { return true; }; 27 | clientHandler.SslProtocols = SslProtocols.None; 28 | } 29 | 30 | #region UAPool 31 | 32 | private static readonly List UAPool = 33 | [ 34 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60", 35 | "Opera/8.0 (Windows NT 5.1; U; en)", 36 | "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50", 37 | "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50", 38 | "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0", 39 | "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", 40 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", 41 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36", 42 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11", 43 | "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", 44 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36", 45 | "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko", 46 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 UBrowser/4.0.3214.0 Safari/537.36", 47 | "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)", 48 | "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)", 49 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", 50 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.55", 51 | ]; 52 | 53 | private static string GetUserAgent() 54 | { 55 | return UAPool[new Random().Next(0, UAPool.Count)]; 56 | } 57 | 58 | #endregion UAPool 59 | 60 | public async ValueTask GetAsync(string url) 61 | { 62 | if (IsInternetConnected) 63 | { 64 | httpClient.DefaultRequestHeaders.Clear(); 65 | httpClient.DefaultRequestHeaders.Add("User-Agent", GetUserAgent()); 66 | using HttpResponseMessage result = await httpClient.GetAsync(url); 67 | if (result.IsSuccessStatusCode) 68 | { 69 | return await result.Content.ReadAsStringAsync(); 70 | } 71 | else 72 | { 73 | return "Error:" + result.StatusCode.ToString(); 74 | } 75 | } 76 | else 77 | { 78 | return "Error:ERR_CONNECTION_TIMED_OUT"; 79 | } 80 | } 81 | 82 | public async ValueTask GetPicture(string url) 83 | { 84 | if (IsInternetConnected) 85 | { 86 | httpClient.DefaultRequestHeaders.Clear(); 87 | httpClient.DefaultRequestHeaders.Add("User-Agent", GetUserAgent()); 88 | try 89 | { 90 | using HttpResponseMessage result = await httpClient.GetAsync(url); 91 | if (result.IsSuccessStatusCode) 92 | { 93 | return await result.Content.ReadAsByteArrayAsync(); 94 | } 95 | else 96 | { 97 | return []; 98 | } 99 | } 100 | catch 101 | { 102 | return []; 103 | } 104 | } 105 | else 106 | { 107 | return []; 108 | } 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /SmartLibrary/Helpers/ResourceManager.cs: -------------------------------------------------------------------------------- 1 | namespace SmartLibrary.Helpers 2 | { 3 | public static class ResourceManager 4 | { 5 | private static string currentTheme = string.Empty; 6 | private static ResourceDictionary? currentThemeResource; 7 | 8 | public static void UpdateTheme(string theme) 9 | { 10 | currentTheme = theme.ToLower(); 11 | Application.Current.Resources.MergedDictionaries.Remove(currentThemeResource); 12 | currentThemeResource = new ResourceDictionary { Source = new Uri($"pack://application:,,,/Style/{currentTheme}.xaml") }; 13 | Application.Current.Resources.MergedDictionaries.Add(currentThemeResource); 14 | } 15 | 16 | public static string CurrentTheme 17 | { 18 | get { return currentTheme; } 19 | } 20 | 21 | public static string EmptyImage 22 | { 23 | get { return $"pack://application:,,,/Assets/DynamicPic/{currentTheme}/PictureEmpty.png"; } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /SmartLibrary/Models/BookInfo.cs: -------------------------------------------------------------------------------- 1 | namespace SmartLibrary.Models 2 | { 3 | public record class BookInfo 4 | { 5 | public string Isbn { get; set; } 6 | public string BookName { get; set; } 7 | public string Author { get; set; } 8 | public string? Press { get; set; } 9 | public string? PressDate { get; set; } 10 | public string? PressPlace { get; set; } 11 | public string? ClcName { get; set; } 12 | public string? Price { get; set; } 13 | public string? BookDesc { get; set; } 14 | public string? Pages { get; set; } 15 | public string? Keyword { get; set; } 16 | public string? Language { get; set; } 17 | public string? Picture { get; set; } 18 | public long ShelfNumber { get; set; } 19 | public bool IsBorrowed { get; set; } 20 | 21 | public BookInfo() 22 | { 23 | Isbn = string.Empty; 24 | BookName = string.Empty; 25 | Author = string.Empty; 26 | } 27 | 28 | public BookInfo(string isbn, string bookName, string author, string press, string pressDate, string pressPlace, string price, string clcName, string bookDesc, string pages, string keyword, string language, long shelfNumber, bool isBorrowed, string picture) 29 | { 30 | Isbn = isbn; 31 | BookName = bookName; 32 | ShelfNumber = shelfNumber; 33 | IsBorrowed = isBorrowed; 34 | Author = author; 35 | Press = press; 36 | PressDate = pressDate; 37 | PressPlace = pressPlace; 38 | ClcName = clcName; 39 | Price = price; 40 | BookDesc = bookDesc; 41 | Pages = pages; 42 | Keyword = keyword; 43 | Picture = picture; 44 | Language = language; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /SmartLibrary/Models/BookInfoSimple.cs: -------------------------------------------------------------------------------- 1 | namespace SmartLibrary.Models 2 | { 3 | public record class BookInfoSimple 4 | { 5 | public string Isbn { get; set; } 6 | public string BookName { get; set; } 7 | public string Author { get; set; } 8 | public long ShelfNumber { get; set; } 9 | public bool IsBorrowed { get; set; } 10 | 11 | public BookInfoSimple(string isbn, string bookName, string author, long shelfNumber, bool isBorrowed) 12 | { 13 | Isbn = isbn; 14 | BookName = bookName; 15 | Author = author; 16 | ShelfNumber = shelfNumber; 17 | IsBorrowed = isBorrowed; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /SmartLibrary/Models/BookShelfInfo.cs: -------------------------------------------------------------------------------- 1 | namespace SmartLibrary.Models 2 | { 3 | public record class BookShelfInfo 4 | { 5 | public string Isbn { get; set; } 6 | public string BookName { get; set; } 7 | public string Author { get; set; } 8 | public string Press { get; set; } 9 | public string Picture { get; set; } 10 | 11 | public BookShelfInfo(string isbn, string bookName, string author, string press, string picture) 12 | { 13 | Isbn = isbn; 14 | BookName = bookName; 15 | Author = author; 16 | Press = press; 17 | Picture = picture; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SmartLibrary/Models/FaceInfoSimple.cs: -------------------------------------------------------------------------------- 1 | namespace SmartLibrary.Models 2 | { 3 | public record class FaceInfoSimple 4 | { 5 | public string Uid { get; set; } 6 | public string Name { get; set; } 7 | public string? Sex { get; set; } 8 | public string? Age { get; set; } 9 | public string? JoinTime { get; set; } 10 | 11 | public FaceInfoSimple(string uid, string name, string? sex, string? age, string? joinTime) 12 | { 13 | Uid = uid; 14 | Name = name; 15 | Sex = sex; 16 | Age = age; 17 | JoinTime = joinTime; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SmartLibrary/Models/ImageQueueInfo.cs: -------------------------------------------------------------------------------- 1 | using Wpf.Ui.Controls; 2 | 3 | namespace SmartLibrary.Models 4 | { 5 | public record class ImageQueueInfo 6 | { 7 | public required Image Image { get; set; } 8 | public required string Isbn { get; set; } 9 | public required string Url { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SmartLibrary/Services/ApplicationHostService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using Shared.Services.Contracts; 4 | using SmartLibrary.Views; 5 | using SmartLibrary.Views.Pages; 6 | 7 | namespace SmartLibrary.Services 8 | { 9 | public class ApplicationHostService : IHostedService 10 | { 11 | private readonly IServiceProvider _serviceProvider; 12 | 13 | public ApplicationHostService(IServiceProvider serviceProvider) 14 | { 15 | _serviceProvider = serviceProvider; 16 | } 17 | 18 | public Task StartAsync(CancellationToken cancellationToken) 19 | { 20 | return HandleActivationAsync(); 21 | } 22 | 23 | public Task StopAsync(CancellationToken cancellationToken) 24 | { 25 | return Task.CompletedTask; 26 | } 27 | 28 | private Task HandleActivationAsync() 29 | { 30 | if (Application.Current.Windows.OfType().Any()) 31 | { 32 | return Task.CompletedTask; 33 | } 34 | 35 | IWindow mainWindow = _serviceProvider.GetRequiredService(); 36 | mainWindow.Loaded += (sender, _) => { if (sender is MainWindow mainWindow) { mainWindow.RootNavigation.Navigate(typeof(Home)); } }; 37 | mainWindow?.Show(); 38 | return Task.CompletedTask; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SmartLibrary/SmartLibrary.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net9.0-windows10.0.26100.0 6 | 10.0.18362.0 7 | enable 8 | true 9 | true 10 | enable 11 | x64 12 | ..\bin 13 | 14 | 15 | 16 | applicationIcon.ico 17 | Smart library 18 | Copyright © 2025 赵杨磊.All Rights Reserved. 19 | 1.0.0.25122 20 | 1.0.0.25122 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | tlbimp 80 | 0 81 | 1 82 | f935dc20-1cf0-11d0-adb9-00c04fd58a0b 83 | 0 84 | false 85 | true 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /SmartLibrary/Style/dark.xaml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /SmartLibrary/Style/light.xaml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /SmartLibrary/Usings.cs: -------------------------------------------------------------------------------- 1 | global using CommunityToolkit.Mvvm.ComponentModel; 2 | global using CommunityToolkit.Mvvm.Input; 3 | global using System.Windows; -------------------------------------------------------------------------------- /SmartLibrary/ViewModels/EditBookViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.Messaging; 2 | using Microsoft.Win32; 3 | using SmartLibrary.Helpers; 4 | using SmartLibrary.Views.Pages; 5 | using System.IO; 6 | using System.Text; 7 | using Wpf.Ui; 8 | using Wpf.Ui.Controls; 9 | using Wpf.Ui.Extensions; 10 | 11 | namespace SmartLibrary.ViewModels 12 | { 13 | public partial class EditBookViewModel : ObservableObject 14 | { 15 | private readonly INavigationService _navigationService; 16 | private readonly ISnackbarService _snackbarService; 17 | private readonly IContentDialogService _contentDialogService; 18 | private readonly BooksDb BooksDb = BooksDb.GetDatabase("books.smartlibrary"); 19 | private readonly LocalStorage localStorage = new(); 20 | 21 | [ObservableProperty] 22 | private bool _isFlyoutOpen = false; 23 | 24 | [ObservableProperty] 25 | private bool _isPictureLoading = false; 26 | 27 | [ObservableProperty] 28 | public bool _isEditButtonEnabled = false; 29 | 30 | [ObservableProperty] 31 | private string _isbnText = string.Empty; 32 | 33 | [ObservableProperty] 34 | private string _bookNameText = string.Empty; 35 | 36 | [ObservableProperty] 37 | private string _authorText = string.Empty; 38 | 39 | [ObservableProperty] 40 | private string _pressText = string.Empty; 41 | 42 | [ObservableProperty] 43 | private string _bookName = string.Empty; 44 | 45 | [ObservableProperty] 46 | private string _author = string.Empty; 47 | 48 | [ObservableProperty] 49 | private string _press = string.Empty; 50 | 51 | [ObservableProperty] 52 | private string _pressDate = string.Empty; 53 | 54 | [ObservableProperty] 55 | private string _pressPlace = string.Empty; 56 | 57 | [ObservableProperty] 58 | private string _price = string.Empty; 59 | 60 | [ObservableProperty] 61 | private string _pages = string.Empty; 62 | 63 | [ObservableProperty] 64 | private string _keyword = string.Empty; 65 | 66 | [ObservableProperty] 67 | private string _clcName = string.Empty; 68 | 69 | [ObservableProperty] 70 | private string _bookDesc = string.Empty; 71 | 72 | [ObservableProperty] 73 | private string _language = string.Empty; 74 | 75 | [ObservableProperty] 76 | private string _picture = string.Empty; 77 | 78 | private string PictureUrl = string.Empty; 79 | 80 | [ObservableProperty] 81 | private string _shelfNum = string.Empty; 82 | 83 | [ObservableProperty] 84 | private bool _isBorrowed = false; 85 | 86 | public EditBookViewModel(INavigationService navigationService, ISnackbarService snackbarService, IContentDialogService contentDialogService) 87 | { 88 | _navigationService = navigationService; 89 | _snackbarService = snackbarService; 90 | _contentDialogService = contentDialogService; 91 | WeakReferenceMessenger.Default.Register(this, "EditBook", OnMessageReceived); 92 | localStorage.LoadingCompleted += LoadingCompleted; 93 | } 94 | 95 | private async void OnMessageReceived(object recipient, string message) 96 | { 97 | IsbnText = message; 98 | Models.BookInfo bookInfo = await BooksDb.GetOneBookInfoAsync(IsbnText); 99 | BookName = bookInfo.BookName; 100 | Author = bookInfo.Author; 101 | Press = bookInfo.Press ?? string.Empty; 102 | PressDate = bookInfo.PressDate ?? string.Empty; 103 | PressPlace = bookInfo.PressPlace ?? string.Empty; 104 | Price = bookInfo.Price ?? string.Empty; 105 | ClcName = bookInfo.ClcName ?? string.Empty; 106 | Keyword = bookInfo.Keyword ?? string.Empty; 107 | Pages = bookInfo.Pages ?? string.Empty; 108 | BookDesc = bookInfo.BookDesc ?? string.Empty; 109 | Language = bookInfo.Language ?? string.Empty; 110 | PictureUrl = bookInfo.Picture ?? string.Empty; 111 | 112 | IsPictureLoading = true; 113 | localStorage.GetPicture(IsbnText, bookInfo.Picture); 114 | 115 | ShelfNum = bookInfo.ShelfNumber.ToString(); 116 | IsBorrowed = bookInfo.IsBorrowed; 117 | WeakReferenceMessenger.Default.Unregister(this); 118 | } 119 | 120 | private void LoadingCompleted(string path) 121 | { 122 | Picture = path; 123 | IsPictureLoading = false; 124 | } 125 | 126 | [RelayCommand] 127 | private void ShowFlyout() 128 | { 129 | IsFlyoutOpen = true; 130 | } 131 | 132 | [RelayCommand] 133 | private void OnSelectPictureButtonClick() 134 | { 135 | IsFlyoutOpen = false; 136 | OpenFileDialog openFileDialog = new() 137 | { 138 | Title = "选择图书封面图片", 139 | Filter = "图像文件|*.jpg;*.png;*.jpeg;*.bmp|所有文件|*.*", 140 | }; 141 | if (openFileDialog.ShowDialog() == true) 142 | { 143 | if (File.Exists(openFileDialog.FileName)) 144 | { 145 | Picture = openFileDialog.FileName; 146 | PictureUrl = openFileDialog.FileName; 147 | IsEditButtonEnabled = true; 148 | } 149 | } 150 | } 151 | 152 | [RelayCommand] 153 | private void OnCleanPictureButtonClick() 154 | { 155 | IsFlyoutOpen = false; 156 | PictureUrl = string.Empty; 157 | Picture = ResourceManager.EmptyImage; 158 | IsEditButtonEnabled = true; 159 | } 160 | 161 | [RelayCommand] 162 | private void OnBorrowedButtonClick() 163 | { 164 | IsBorrowed = !IsBorrowed; 165 | IsEditButtonEnabled = true; 166 | } 167 | 168 | [RelayCommand] 169 | private async Task OnEditBookButtonClick() 170 | { 171 | StringBuilder tip = new(); 172 | if (string.IsNullOrEmpty(BookName)) 173 | { 174 | tip.AppendLine("书名不能为空!"); 175 | } 176 | if (string.IsNullOrEmpty(Author)) 177 | { 178 | tip.AppendLine("作者不能为空!"); 179 | } 180 | if (string.IsNullOrEmpty(ShelfNum)) 181 | { 182 | tip.AppendLine("书架号不能为空!"); 183 | } 184 | 185 | if (string.IsNullOrEmpty(tip.ToString())) 186 | { 187 | Models.BookInfo bookInfo = new() 188 | { 189 | Isbn = IsbnText, 190 | BookName = BookName, 191 | Author = Author, 192 | Press = Press, 193 | PressDate = PressDate, 194 | PressPlace = PressPlace, 195 | Price = Price, 196 | ClcName = ClcName, 197 | Keyword = Keyword, 198 | Pages = Pages, 199 | BookDesc = BookDesc, 200 | Language = Language, 201 | Picture = LocalStorage.AddPicture(IsbnText, PictureUrl), 202 | ShelfNumber = int.Parse(ShelfNum), 203 | IsBorrowed = IsBorrowed, 204 | }; 205 | BooksDb.UpdateAsync(bookInfo); 206 | System.Media.SystemSounds.Asterisk.Play(); 207 | _snackbarService.Show("更改成功", $"书籍《{BookName}》信息已更新。", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Info16), TimeSpan.FromSeconds(3)); 208 | IsEditButtonEnabled = false; 209 | WeakReferenceMessenger.Default.Send("refresh", "BookManage"); 210 | WeakReferenceMessenger.Default.Send("refresh", "Bookshelf"); 211 | WeakReferenceMessenger.Default.Send("." + IsbnText, "Borrow_Return_Book"); 212 | } 213 | else 214 | { 215 | string content = "您必须完善以下书籍信息, 才能更改书籍信息:\n\n" + tip.ToString(); 216 | System.Media.SystemSounds.Asterisk.Play(); 217 | await _contentDialogService.ShowSimpleDialogAsync(new SimpleContentDialogCreateOptions() 218 | { 219 | Title = "更改书籍信息", 220 | Content = content, 221 | CloseButtonText = "去完善", 222 | }); 223 | } 224 | } 225 | 226 | partial void OnBookNameChanged(string value) 227 | { 228 | if (string.IsNullOrEmpty(value)) 229 | { 230 | BookNameText = string.Empty; 231 | } 232 | else 233 | { 234 | BookNameText = $"《{value}》"; 235 | } 236 | } 237 | 238 | partial void OnAuthorChanged(string value) 239 | { 240 | if (string.IsNullOrEmpty(value)) 241 | { 242 | AuthorText = string.Empty; 243 | } 244 | else 245 | { 246 | AuthorText = "作者:" + value; 247 | } 248 | } 249 | 250 | partial void OnPressChanged(string value) 251 | { 252 | if (string.IsNullOrEmpty(value)) 253 | { 254 | PressText = string.Empty; 255 | } 256 | else 257 | { 258 | PressText = "出版社:" + value; 259 | } 260 | } 261 | 262 | [RelayCommand] 263 | private void NavigateBack() 264 | { 265 | localStorage.LoadingCompleted -= LoadingCompleted; 266 | _navigationService.Navigate(typeof(BookManage)); 267 | } 268 | } 269 | } -------------------------------------------------------------------------------- /SmartLibrary/ViewModels/EditUserViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.Messaging; 2 | using Shared.Helpers; 3 | using Shared.Models; 4 | using System.Windows.Media.Imaging; 5 | using Wpf.Ui; 6 | using Wpf.Ui.Controls; 7 | using Wpf.Ui.Extensions; 8 | 9 | namespace SmartLibrary.ViewModels 10 | { 11 | public partial class EditUserViewModel : ObservableObject 12 | { 13 | private readonly INavigationService _navigationService; 14 | private readonly ISnackbarService _snackbarService; 15 | private readonly IContentDialogService _contentDialogService; 16 | private readonly UsersDb UsersDb = UsersDb.GetDatabase("users.smartmanager"); 17 | private bool _initial = true; 18 | 19 | [ObservableProperty] 20 | private string _uID = string.Empty; 21 | 22 | [ObservableProperty] 23 | private string _name = string.Empty; 24 | 25 | [ObservableProperty] 26 | private string? _sex = string.Empty; 27 | 28 | [ObservableProperty] 29 | private string? _age = string.Empty; 30 | 31 | [ObservableProperty] 32 | private string? _joinTime = string.Empty; 33 | 34 | [ObservableProperty] 35 | private string _feature = string.Empty; 36 | 37 | [ObservableProperty] 38 | private BitmapImage _faceImage = new(); 39 | 40 | [ObservableProperty] 41 | public bool _isEditButtonEnabled = false; 42 | 43 | public EditUserViewModel(INavigationService navigationService, ISnackbarService snackbarService, IContentDialogService contentDialogService) 44 | { 45 | _navigationService = navigationService; 46 | _snackbarService = snackbarService; 47 | _contentDialogService = contentDialogService; 48 | 49 | WeakReferenceMessenger.Default.Register(this, "EditFace", OnMessageReceived); 50 | } 51 | 52 | private async void OnMessageReceived(object recipient, string message) 53 | { 54 | User user = await UsersDb.GetOneUserAsync(message); 55 | UID = user.Uid; 56 | Name = user.Name; 57 | Sex = user.Sex; 58 | Age = user.Age; 59 | JoinTime = user.JoinTime; 60 | Feature = user.Feature; 61 | FaceImage = user.FaceImage; 62 | WeakReferenceMessenger.Default.Unregister(this); 63 | _initial = false; 64 | } 65 | 66 | [RelayCommand] 67 | private async Task EditFaceButtonClick() 68 | { 69 | if (string.IsNullOrEmpty(Name)) 70 | { 71 | System.Media.SystemSounds.Asterisk.Play(); 72 | await _contentDialogService.ShowSimpleDialogAsync(new SimpleContentDialogCreateOptions() 73 | { 74 | Title = "编辑信息", 75 | Content = "您必须完善以下书籍信息, 才能更改用户信息:\n\n姓名不能为空!", 76 | CloseButtonText = "去完善", 77 | }); 78 | } 79 | else 80 | { 81 | UsersDb.UpdateFaceAsync(new(UID, Name, Sex, Age, JoinTime, Feature, FaceImage)); 82 | 83 | System.Media.SystemSounds.Asterisk.Play(); 84 | _snackbarService.Show("更改成功", $"用户 {Name} 信息已更改。", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Info16), TimeSpan.FromSeconds(3)); 85 | WeakReferenceMessenger.Default.Send("refresh", "FaceManage"); 86 | IsEditButtonEnabled = false; 87 | } 88 | } 89 | 90 | [RelayCommand] 91 | private void GoBack() 92 | { 93 | _navigationService.GoBack(); 94 | } 95 | 96 | partial void OnAgeChanged(string? value) 97 | { 98 | if (!_initial) 99 | { 100 | IsEditButtonEnabled = true; 101 | } 102 | } 103 | 104 | partial void OnJoinTimeChanged(string? value) 105 | { 106 | if (!_initial) 107 | { 108 | IsEditButtonEnabled = true; 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /SmartLibrary/ViewModels/HomeViewModel.cs: -------------------------------------------------------------------------------- 1 | using Wpf.Ui; 2 | 3 | namespace SmartLibrary.ViewModels 4 | { 5 | public partial class HomeViewModel : ObservableObject 6 | { 7 | private readonly INavigationService _navigationService; 8 | 9 | public HomeViewModel(INavigationService navigationService) 10 | { 11 | _navigationService = navigationService; 12 | } 13 | 14 | [RelayCommand] 15 | private void OnCardClick(string parameter) 16 | { 17 | if (parameter == "我要找书") 18 | { 19 | _navigationService.Navigate(typeof(Views.Pages.Bookshelf)); 20 | } 21 | else if (parameter == "我要借/还书") 22 | { 23 | _navigationService.Navigate(typeof(Views.Pages.Borrow_Return_Book)); 24 | } 25 | else if (parameter == "管理") 26 | { 27 | _navigationService.Navigate(typeof(Views.Pages.BookManage)); 28 | } 29 | else if (parameter == "设置") 30 | { 31 | _navigationService.Navigate(typeof(Views.Pages.Settings)); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /SmartLibrary/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using Shared.Helpers; 2 | using System.Collections.ObjectModel; 3 | using Wpf.Ui.Controls; 4 | 5 | namespace SmartLibrary.ViewModels 6 | { 7 | public partial class MainWindowViewModel : ObservableObject 8 | { 9 | [ObservableProperty] 10 | private ObservableCollection _navigationItems = []; 11 | 12 | [ObservableProperty] 13 | private ObservableCollection _navigationFooter = []; 14 | 15 | public MainWindowViewModel() 16 | { 17 | NavigationItems = 18 | [ 19 | new NavigationViewItem() 20 | { 21 | Content = "主页", 22 | Icon = new SymbolIcon { Symbol = SymbolRegular.Home24 }, 23 | TargetPageType = typeof(Views.Pages.Home), 24 | }, 25 | new NavigationViewItem() 26 | { 27 | Content = "书架", 28 | Icon = new SymbolIcon { Symbol = SymbolRegular.Library24 }, 29 | TargetPageType = typeof(Views.Pages.Bookshelf) 30 | }, 31 | new NavigationViewItem() 32 | { 33 | Content = "借还", 34 | Icon = new SymbolIcon { Symbol = SymbolRegular.CheckboxCheckedSync20 }, 35 | TargetPageType = typeof(Views.Pages.Borrow_Return_Book) 36 | } 37 | ]; 38 | 39 | if (SettingsHelper.GetBoolean("IsAdministrator")) 40 | { 41 | NavigationFooter.Add(new NavigationViewItem() 42 | { 43 | Content = "用户", 44 | Icon = new SymbolIcon { Symbol = SymbolRegular.Accessibility24 }, 45 | TargetPageType = typeof (Views.Pages.UserManage) 46 | }); 47 | NavigationFooter.Add(new NavigationViewItem() 48 | { 49 | Content = "管理", 50 | Icon = new SymbolIcon { Symbol = SymbolRegular.Apps24 }, 51 | TargetPageType = typeof(Views.Pages.BookManage) 52 | }); 53 | } 54 | 55 | NavigationFooter.Add(new NavigationViewItem() 56 | { 57 | Content = "设置", 58 | Icon = new SymbolIcon { Symbol = SymbolRegular.Settings24 }, 59 | TargetPageType = typeof(Views.Pages.Settings) 60 | }); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /SmartLibrary/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /SmartLibrary/Views/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using Shared.Appearance; 2 | using Shared.Helpers; 3 | using Shared.Services.Contracts; 4 | using SmartLibrary.Helpers; 5 | using SmartLibrary.ViewModels; 6 | using System.Runtime.InteropServices; 7 | using System.Windows.Interop; 8 | using Wpf.Ui; 9 | using Wpf.Ui.Appearance; 10 | 11 | namespace SmartLibrary.Views 12 | { 13 | public partial class MainWindow : IWindow 14 | { 15 | public MainWindowViewModel ViewModel { get; } 16 | 17 | public MainWindow(MainWindowViewModel viewModel, 18 | INavigationService navigationService, 19 | IServiceProvider serviceProvider, 20 | ISnackbarService snackbarService, 21 | IContentDialogService contentDialogService) 22 | { 23 | ViewModel = viewModel; 24 | DataContext = this; 25 | InitializeComponent(); 26 | 27 | RootNavigation.SetServiceProvider(serviceProvider); 28 | navigationService.SetNavigationControl(RootNavigation); 29 | snackbarService.SetSnackbarPresenter(SnackbarPresenter); 30 | contentDialogService.SetDialogHost(RootContentDialog); 31 | 32 | Loaded += Window_Loaded; 33 | } 34 | 35 | private void Window_Loaded(object sender, RoutedEventArgs e) 36 | { 37 | LoadingSettings(); 38 | #if RELEASE 39 | WindowInteropHelper helper = new(this); 40 | HwndSource hwndSource = HwndSource.FromHwnd(helper.Handle); 41 | hwndSource.AddHook(new HwndSourceHook(WndProc)); 42 | #endif 43 | } 44 | 45 | private void LoadingSettings() 46 | { 47 | ApplicationTheme theme = Utils.GetUserApplicationTheme(SettingsHelper.GetConfig("Theme")); 48 | if (SettingsHelper.GetConfig("Theme") == "System") 49 | { 50 | SystemThemeWatcher.Watch(this); 51 | ThemeManager.Changed += (t, _) => { ResourceManager.UpdateTheme(Utils.GetUserApplicationTheme(t.ToString()).ToString()); }; 52 | } 53 | ResourceManager.UpdateTheme(theme.ToString()); 54 | ThemeManager.Apply(theme, Utils.GetUserBackdrop(SettingsHelper.GetConfig("Backdrop"))); 55 | if (SettingsHelper.GetBoolean("IsCustomizedAccentColor")) 56 | { 57 | ApplicationAccentColorManager.Apply(Utils.StringToColor(SettingsHelper.GetConfig("CustomizedAccentColor")), theme); 58 | } 59 | } 60 | 61 | private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) 62 | { 63 | if (msg == Win32Helper.WM_COPYDATA) 64 | { 65 | object? o = Marshal.PtrToStructure(lparam); 66 | if (o != null) 67 | { 68 | Win32Helper.COPYDATASTRUCT cds = (Win32Helper.COPYDATASTRUCT)o; 69 | string? receivedMessage = Marshal.PtrToStringUni(cds.lpData); 70 | if (receivedMessage == "SmartLibrary") 71 | { 72 | if (WindowState == WindowState.Minimized || Visibility != Visibility.Visible) 73 | { 74 | Show(); 75 | WindowState = WindowState.Normal; 76 | } 77 | Activate(); 78 | Topmost = true; 79 | Topmost = false; 80 | Focus(); 81 | } 82 | } 83 | } 84 | return IntPtr.Zero; 85 | } 86 | 87 | protected override void OnClosed(EventArgs e) 88 | { 89 | FaceRecognition.CloseCamera(); 90 | base.OnClosed(e); 91 | Application.Current.Shutdown(); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /SmartLibrary/Views/Pages/AddBook.xaml.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.ViewModels; 2 | using System.Windows.Input; 3 | using Wpf.Ui.Abstractions.Controls; 4 | using Wpf.Ui.Controls; 5 | 6 | namespace SmartLibrary.Views.Pages 7 | { 8 | public partial class AddBook : INavigableView 9 | { 10 | public AddBookViewModel ViewModel { get; } 11 | 12 | public AddBook(AddBookViewModel viewModel) 13 | { 14 | ViewModel = viewModel; 15 | DataContext = this; 16 | InitializeComponent(); 17 | } 18 | 19 | private void IsbnBox_PreviewKeyDown(object sender, KeyEventArgs e) 20 | { 21 | if (e.Key == Key.Enter) 22 | { 23 | if (sender is TextBox isbnBox) 24 | { 25 | if (string.IsNullOrEmpty(isbnBox.Text)) 26 | { 27 | XuNiBox.Focus(); 28 | } 29 | else if (isbnBox.Text.Length == 13) 30 | { 31 | XuNiBox.Focus(); 32 | _ = ViewModel.OnSearchButtonClick(); 33 | } 34 | } 35 | } 36 | } 37 | 38 | private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) 39 | { 40 | if (e.Key == Key.Enter) 41 | { 42 | XuNiBox.Focus(); 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /SmartLibrary/Views/Pages/AddUser.xaml.cs: -------------------------------------------------------------------------------- 1 | using Shared.Helpers; 2 | using SmartLibrary.ViewModels; 3 | using System.Windows.Controls; 4 | using System.Windows.Input; 5 | using System.Windows.Media; 6 | using System.Windows.Media.Imaging; 7 | using Wpf.Ui.Abstractions.Controls; 8 | 9 | namespace SmartLibrary.Views.Pages 10 | { 11 | public partial class AddUser : INavigableView 12 | { 13 | public AddUserViewModel ViewModel { get; set; } 14 | private ScrollViewer? scroll; 15 | 16 | public AddUser(AddUserViewModel viewModel) 17 | { 18 | ViewModel = viewModel; 19 | DataContext = this; 20 | InitializeComponent(); 21 | } 22 | 23 | private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) 24 | { 25 | if (e.Key == Key.Enter) 26 | { 27 | XuNiBox.Focus(); 28 | } 29 | } 30 | 31 | private void OpenCameraClick(object sender, RoutedEventArgs e) 32 | { 33 | ViewModel.OpenCamera(CameraImage, MaskImage); 34 | } 35 | 36 | private void CaptureFaceClick(object sender, RoutedEventArgs e) 37 | { 38 | ViewModel.CaptureFace(CameraImage); 39 | } 40 | 41 | private void AddFaceClick(object sender, RoutedEventArgs e) 42 | { 43 | ViewModel.AddFace(); 44 | } 45 | 46 | private void FaceListView_MouseWheel(object sender, MouseWheelEventArgs e) 47 | { 48 | if (scroll == null) 49 | { 50 | scroll = Utils.FindVisualChild(FaceListView); 51 | } 52 | else 53 | { 54 | if (e.Delta < 0) 55 | { 56 | scroll.LineRight(); 57 | } 58 | else 59 | { 60 | scroll.LineLeft(); 61 | } 62 | scroll.ScrollToTop(); 63 | } 64 | } 65 | 66 | private void DrawFaceRectangle_Unchecked(object sender, RoutedEventArgs e) 67 | { 68 | if (!FaceComparison_CheckBox.IsChecked ?? false) 69 | { 70 | BitmapSource mask = BitmapImage.Create(2, 2, 96, 96, PixelFormats.Indexed1, new BitmapPalette(new List { Colors.Transparent }), new byte[] { 0, 0, 0, 0 }, 1); 71 | MaskImage.Source = mask; 72 | } 73 | 74 | } 75 | 76 | private void FaceComparison_Unchecked(object sender, RoutedEventArgs e) 77 | { 78 | if (!DrawFaceRectangle_CheckBox.IsChecked ?? false) 79 | { 80 | BitmapSource mask = BitmapImage.Create(2, 2, 96, 96, PixelFormats.Indexed1, new BitmapPalette(new List { Colors.Transparent }), new byte[] { 0, 0, 0, 0 }, 1); 81 | MaskImage.Source = mask; 82 | } 83 | } 84 | 85 | private void DeviceComboBox_DropDownOpened(object sender, EventArgs e) 86 | { 87 | ViewModel.DeviceComboBox_DropDownOpened(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /SmartLibrary/Views/Pages/BookManage.xaml.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.Models; 2 | using SmartLibrary.ViewModels; 3 | using System.Data; 4 | using System.IO; 5 | using System.Windows.Input; 6 | using Wpf.Ui.Abstractions.Controls; 7 | using Wpf.Ui.Controls; 8 | 9 | namespace SmartLibrary.Views.Pages 10 | { 11 | public partial class BookManage : INavigableView 12 | { 13 | private readonly BookInfoSimple bookInfo = new(string.Empty, string.Empty, string.Empty, 0, false); 14 | 15 | public BookManageViewModel ViewModel { get; } 16 | 17 | public BookManage(BookManageViewModel viewModel) 18 | { 19 | ViewModel = viewModel; 20 | DataContext = this; 21 | InitializeComponent(); 22 | } 23 | 24 | private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) 25 | { 26 | if (e.Key == Key.Enter) 27 | { 28 | ViewModel.GotoTargetPage(textBox.Text); 29 | XuNiBox.Focus(); 30 | } 31 | } 32 | 33 | private void SearchBox_PreviewKeyDown(object sender, KeyEventArgs e) 34 | { 35 | if (e.Key == Key.Enter) 36 | { 37 | XuNiBox.Focus(); 38 | } 39 | } 40 | 41 | private void DataGrid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) 42 | { 43 | if (DataGrid.SelectedItem == null) { DelButton.IsEnabled = false; } 44 | else { DelButton.IsEnabled = true; } 45 | } 46 | 47 | private void DataGrid_BeginningEdit(object sender, System.Windows.Controls.DataGridBeginningEditEventArgs e) 48 | { 49 | if (e.Row.Item is DataRowView dataRowView) 50 | { 51 | bookInfo.Isbn = (string)dataRowView[0]; 52 | bookInfo.BookName = (string)dataRowView[1]; 53 | bookInfo.Author = (string)dataRowView[2]; 54 | bookInfo.ShelfNumber = (long)dataRowView[3]; 55 | bookInfo.IsBorrowed = Convert.ToBoolean(dataRowView[4]); 56 | } 57 | } 58 | 59 | private void DataGrid_RowEditEnding(object sender, System.Windows.Controls.DataGridRowEditEndingEventArgs e) 60 | { 61 | if (e.Row.Item is DataRowView dataRowView) 62 | { 63 | if (string.IsNullOrEmpty(dataRowView[1].ToString())) 64 | { 65 | dataRowView[1] = bookInfo.BookName; 66 | } 67 | if (string.IsNullOrEmpty(dataRowView[2].ToString())) 68 | { 69 | dataRowView[2] = bookInfo.Author; 70 | } 71 | 72 | if ((string)dataRowView[1] != bookInfo.BookName || (string)dataRowView[2] != bookInfo.Author || (long)dataRowView[3] != bookInfo.ShelfNumber) 73 | { 74 | bookInfo.Isbn = (string)dataRowView[0]; 75 | bookInfo.BookName = (string)dataRowView[1]; 76 | bookInfo.Author = (string)dataRowView[2]; 77 | bookInfo.ShelfNumber = (long)dataRowView[3]; 78 | bookInfo.IsBorrowed = Convert.ToBoolean(dataRowView[4]); 79 | ViewModel.UpdateSimple(bookInfo); 80 | } 81 | } 82 | } 83 | 84 | private void CheckBox_Click(object sender, RoutedEventArgs e) 85 | { 86 | if (DataGrid.SelectedItem is DataRowView dataRowView) 87 | { 88 | string isbn = (string)dataRowView[0]; 89 | bool isBorrowed = Convert.ToBoolean(dataRowView[4]); 90 | ViewModel.CheckBox_Click(isbn, isBorrowed); 91 | } 92 | } 93 | 94 | private void Page_DragOver(object sender, DragEventArgs e) 95 | { 96 | if (e.Data.GetDataPresent(DataFormats.FileDrop)) 97 | { 98 | e.Effects = DragDropEffects.Link; 99 | Array a = (Array)e.Data.GetData(DataFormats.FileDrop); 100 | foreach (string filepath in a) 101 | { 102 | if (Directory.Exists(filepath)) 103 | { 104 | e.Effects = DragDropEffects.None; 105 | e.Handled = true; 106 | return; 107 | } 108 | if (!filepath.EndsWith("smartlibrary")) 109 | { 110 | e.Effects = DragDropEffects.None; 111 | e.Handled = true; 112 | return; 113 | } 114 | } 115 | e.Effects = DragDropEffects.Link; 116 | } 117 | else 118 | { 119 | e.Effects = DragDropEffects.None; 120 | e.Handled = true; 121 | } 122 | } 123 | 124 | private void Page_Drop(object sender, DragEventArgs e) 125 | { 126 | List files = new((string[])e.Data.GetData(DataFormats.FileDrop)); 127 | ViewModel.DropFileImportAsync(files); 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /SmartLibrary/Views/Pages/Bookshelf.xaml.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.ViewModels; 2 | using System.Windows.Input; 3 | using Wpf.Ui.Abstractions.Controls; 4 | 5 | namespace SmartLibrary.Views.Pages 6 | { 7 | public partial class Bookshelf : INavigableView 8 | { 9 | public BookshelfViewModel ViewModel { get; } 10 | 11 | public Bookshelf(BookshelfViewModel viewModel) 12 | { 13 | ViewModel = viewModel; 14 | DataContext = this; 15 | InitializeComponent(); 16 | } 17 | 18 | private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) 19 | { 20 | if (e.Key == Key.Enter) 21 | { 22 | ViewModel.GotoTargetPage(textBox.Text); 23 | XuNiBox.Focus(); 24 | } 25 | } 26 | 27 | private void SearchBox_PreviewKeyDown(object sender, KeyEventArgs e) 28 | { 29 | if (e.Key == Key.Enter) 30 | { 31 | XuNiBox.Focus(); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /SmartLibrary/Views/Pages/Borrow_Return_Book.xaml.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.ViewModels; 2 | using System.Windows.Input; 3 | using Wpf.Ui.Abstractions.Controls; 4 | using Wpf.Ui.Controls; 5 | 6 | namespace SmartLibrary.Views.Pages 7 | { 8 | public partial class Borrow_Return_Book : INavigableView 9 | { 10 | public Borrow_Return_BookViewModel ViewModel { get; } 11 | 12 | public Borrow_Return_Book(Borrow_Return_BookViewModel viewModel) 13 | { 14 | ViewModel = viewModel; 15 | DataContext = this; 16 | InitializeComponent(); 17 | } 18 | 19 | private void IsbnBox_PreviewKeyDown(object sender, KeyEventArgs e) 20 | { 21 | if (e.Key == Key.Enter) 22 | { 23 | if (sender is TextBox isbnBox) 24 | { 25 | if (string.IsNullOrEmpty(isbnBox.Text)) 26 | { 27 | XuNiBox.Focus(); 28 | } 29 | else if (isbnBox.Text.Length == 13) 30 | { 31 | XuNiBox.Focus(); 32 | _ = ViewModel.OnSearchButtonClick(); 33 | } 34 | } 35 | } 36 | } 37 | 38 | private void BorrowOrReturn_Click(object sender, RoutedEventArgs e) 39 | { 40 | ViewModel.BorrowOrReturn(CameraImage); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /SmartLibrary/Views/Pages/EditBook.xaml.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.ViewModels; 2 | using System.Windows.Input; 3 | using Wpf.Ui.Abstractions.Controls; 4 | 5 | namespace SmartLibrary.Views.Pages 6 | { 7 | public partial class EditBook : INavigableView 8 | { 9 | public EditBookViewModel ViewModel { get; } 10 | 11 | public EditBook(EditBookViewModel viewModel) 12 | { 13 | ViewModel = viewModel; 14 | DataContext = this; 15 | InitializeComponent(); 16 | } 17 | 18 | private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) 19 | { 20 | if (e.Key == Key.Enter) 21 | { 22 | XuNiBox.Focus(); 23 | } 24 | else 25 | { 26 | ViewModel.IsEditButtonEnabled = true; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /SmartLibrary/Views/Pages/EditUser.xaml.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.ViewModels; 2 | using System.Windows.Input; 3 | using Wpf.Ui.Abstractions.Controls; 4 | 5 | namespace SmartLibrary.Views.Pages 6 | { 7 | public partial class EditUser : INavigableView 8 | { 9 | public EditUserViewModel ViewModel { get; set; } 10 | 11 | public EditUser(EditUserViewModel viewModel) 12 | { 13 | ViewModel = viewModel; 14 | DataContext = this; 15 | InitializeComponent(); 16 | } 17 | 18 | private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) 19 | { 20 | if (e.Key == Key.Enter) 21 | { 22 | XuNiBox.Focus(); 23 | } 24 | else 25 | { 26 | ViewModel.IsEditButtonEnabled = true; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SmartLibrary/Views/Pages/Home.xaml: -------------------------------------------------------------------------------- 1 |  18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 50 | 51 | 55 | 56 | 57 | 61 | 62 | 66 | 67 | 68 | 73 | 74 | 78 | 79 | 80 | 81 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 103 | 104 | 105 | 106 | 107 | 108 | 115 | 122 | 123 | 124 | 132 | 133 | 134 | 135 | 136 | 137 | 144 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 167 | 168 | 169 | 170 | 171 | 172 | 179 | 186 | 187 | 188 | 196 | 197 | 198 | 199 | 200 | 201 | 208 | 214 | 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /SmartLibrary/Views/Pages/Home.xaml.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.ViewModels; 2 | using Wpf.Ui.Abstractions.Controls; 3 | 4 | namespace SmartLibrary.Views.Pages 5 | { 6 | public partial class Home : INavigableView 7 | { 8 | public HomeViewModel ViewModel { get; } 9 | 10 | public Home(HomeViewModel viewModel) 11 | { 12 | ViewModel = viewModel; 13 | DataContext = this; 14 | InitializeComponent(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /SmartLibrary/Views/Pages/Settings.xaml.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.ViewModels; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Windows.Input; 6 | using Wpf.Ui.Abstractions.Controls; 7 | 8 | namespace SmartLibrary.Views.Pages 9 | { 10 | public partial class Settings : INavigableView 11 | { 12 | public SettingsViewModel ViewModel { get; } 13 | private static readonly List SystemAccentColor = ["#FFB900", "#FF8C00", "#F7630C", "#CA5010", "#DA3B01", "#EF6950", "#D13438", "#FF4343", "#E74856", "#E81123", "#EA005E", "#C30052", "#E3008C", "#BF0077", "#C239B3", "#9A0089", "#0078D4", "#0063B1", "#8E8CD8", "#6B69D6", "#8764B8", "#744DA9", "#B146C2", "#881798", "#0099BC", "#2D7D96", "#00B7C3", "#038387", "#00B294", "#018574", "#00CC6A", "#10893E", "#7A7574", "#5D5A58", "#68768A", "#515C6B", "#567C73", "#486860", "#498205", "#107C10", "#767676", "#4C4A48", "#69797E", "#4A5459", "#647C64", "#525E54", "#847545", "#7E735F"]; 14 | 15 | public Settings(SettingsViewModel viewModel) 16 | { 17 | ViewModel = viewModel; 18 | DataContext = this; 19 | InitializeComponent(); 20 | AccentColor.ItemsSource = SystemAccentColor; 21 | 22 | AppVersion.Text = Assembly.GetExecutingAssembly().GetName().Version?.ToString(); 23 | DotNetVersion.Content = ".Net " + Environment.Version.ToString(); 24 | WpfUIVersion.Content = "WPF-UI " + (FileVersionInfo.GetVersionInfo("./Wpf.Ui.dll").ProductVersion ?? string.Empty).Split("+")[0]; 25 | } 26 | 27 | private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) 28 | { 29 | if (e.Key == Key.Enter) 30 | { 31 | ViewModel.SetTimeOut(textBox.Text); 32 | XuNiBox.Focus(); 33 | } 34 | } 35 | 36 | private void FileOccupancyExpander_Expanded(object sender, RoutedEventArgs e) 37 | { 38 | ViewModel.FileOccupancyExpander_Expanded(); 39 | } 40 | 41 | private void ColorExpander_Expanded(object sender, RoutedEventArgs e) 42 | { 43 | ViewModel.ColorExpander_Expanded(); 44 | } 45 | 46 | private void AppFolderButton_Click(object sender, RoutedEventArgs e) 47 | { 48 | Process.Start("explorer.exe", Environment.CurrentDirectory); 49 | } 50 | 51 | private void BooksDataButton_Click(object sender, RoutedEventArgs e) 52 | { 53 | string path = Environment.CurrentDirectory + @".\database\"; 54 | if (!Directory.Exists(path)) Directory.CreateDirectory(path); 55 | Process.Start("explorer.exe", path); 56 | } 57 | 58 | private void PictureCacheButton_Click(object sender, RoutedEventArgs e) 59 | { 60 | string path = Environment.CurrentDirectory + @".\pictures\"; 61 | if (!Directory.Exists(path)) Directory.CreateDirectory(path); 62 | Process.Start("explorer.exe", path); 63 | } 64 | 65 | private void TempButton_Click(object sender, RoutedEventArgs e) 66 | { 67 | string path = Environment.CurrentDirectory + @".\temp\"; 68 | if (!Directory.Exists(path)) Directory.CreateDirectory(path); 69 | Process.Start("explorer.exe", path); 70 | } 71 | 72 | private void CopyMailAddress(object sender, MouseButtonEventArgs e) 73 | { 74 | ViewModel.CopyMailAddress(); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /SmartLibrary/Views/Pages/UserManage.xaml.cs: -------------------------------------------------------------------------------- 1 | using SmartLibrary.Models; 2 | using SmartLibrary.ViewModels; 3 | using System.Data; 4 | using System.IO; 5 | using System.Windows.Controls; 6 | using System.Windows.Input; 7 | using Wpf.Ui.Abstractions.Controls; 8 | 9 | namespace SmartLibrary.Views.Pages 10 | { 11 | public partial class UserManage : INavigableView 12 | { 13 | private readonly FaceInfoSimple faceInfo = new(string.Empty, string.Empty, string.Empty, string.Empty, string.Empty); 14 | 15 | public UserManageViewModel ViewModel { get; } 16 | 17 | public UserManage(UserManageViewModel viewModel) 18 | { 19 | ViewModel = viewModel; 20 | DataContext = this; 21 | InitializeComponent(); 22 | } 23 | 24 | private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) 25 | { 26 | if (e.Key == Key.Enter) 27 | { 28 | ViewModel.GotoTargetPage(textBox.Text); 29 | XuNiBox.Focus(); 30 | } 31 | } 32 | 33 | private void SearchBox_PreviewKeyDown(object sender, KeyEventArgs e) 34 | { 35 | if (e.Key == Key.Enter) 36 | { 37 | XuNiBox.Focus(); 38 | } 39 | } 40 | 41 | private void DataGrid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) 42 | { 43 | if (DataGrid.SelectedItem == null) { DelButton.IsEnabled = false; } 44 | else { DelButton.IsEnabled = true; } 45 | } 46 | 47 | private void DataGrid_BeginningEdit(object sender, System.Windows.Controls.DataGridBeginningEditEventArgs e) 48 | { 49 | if (e.Row.Item is DataRowView dataRowView) 50 | { 51 | faceInfo.Name = (string)dataRowView[1]; 52 | faceInfo.Sex = dataRowView[2].ToString(); 53 | faceInfo.Age = dataRowView[3].ToString(); 54 | faceInfo.JoinTime = dataRowView[4].ToString(); 55 | } 56 | } 57 | 58 | private void DataGrid_RowEditEnding(object sender, System.Windows.Controls.DataGridRowEditEndingEventArgs e) 59 | { 60 | if (e.Row.Item is DataRowView dataRowView) 61 | { 62 | if (string.IsNullOrEmpty(dataRowView[1].ToString())) 63 | { 64 | dataRowView[1] = faceInfo.Name; 65 | } 66 | 67 | if ((string)dataRowView[1] != faceInfo.Name || dataRowView[2].ToString() != faceInfo.Sex || dataRowView[3].ToString() != faceInfo.Age || dataRowView[4].ToString() != faceInfo.JoinTime) 68 | { 69 | faceInfo.Uid = (string)dataRowView[0]; 70 | faceInfo.Name = (string)dataRowView[1]; 71 | faceInfo.Sex = dataRowView[2].ToString(); 72 | faceInfo.Age = dataRowView[3].ToString(); 73 | faceInfo.JoinTime = dataRowView[4].ToString(); 74 | ViewModel.UpdateSimple(faceInfo); 75 | } 76 | } 77 | } 78 | 79 | private void Page_DragOver(object sender, DragEventArgs e) 80 | { 81 | if (e.Data.GetDataPresent(DataFormats.FileDrop)) 82 | { 83 | e.Effects = DragDropEffects.Link; 84 | Array a = (Array)e.Data.GetData(DataFormats.FileDrop); 85 | foreach (string filepath in a) 86 | { 87 | if (Directory.Exists(filepath)) 88 | { 89 | e.Effects = DragDropEffects.None; 90 | e.Handled = true; 91 | return; 92 | } 93 | if (!filepath.EndsWith("smartmanager")) 94 | { 95 | e.Effects = DragDropEffects.None; 96 | e.Handled = true; 97 | return; 98 | } 99 | } 100 | e.Effects = DragDropEffects.Link; 101 | } 102 | else 103 | { 104 | e.Effects = DragDropEffects.None; 105 | e.Handled = true; 106 | } 107 | } 108 | 109 | private void Page_Drop(object sender, DragEventArgs e) 110 | { 111 | List files = new((string[])e.Data.GetData(DataFormats.FileDrop)); 112 | ViewModel.DropFileImportAsync(files); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /SmartLibrary/applicationIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/SmartLibrary/applicationIcon.ico -------------------------------------------------------------------------------- /docs/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/docs/book.png -------------------------------------------------------------------------------- /docs/reference.md: -------------------------------------------------------------------------------- 1 | ## 📄 引用 2 | 3 | * [WPF UI](https://github.com/lepoco/wpfui) 4 | * [.NET Community Toolkit](https://github.com/CommunityToolkit/dotnet) 5 | * [EleCho.WpfSuite](https://github.com/OrgEleCho/EleCho.WpfSuite) 6 | * [Hompus.VideoInputDevices](https://github.com/eNeRGy164/VideoInputDevices) 7 | * [Microsoft.Extensions.DependencyInjection](https://github.com/dotnet/runtime/tree/main/src/libraries/Microsoft.Extensions.DependencyInjection) 8 | * [Microsoft.Extensions.Hosting](https://github.com/dotnet/runtime/tree/main/src/libraries/Microsoft.Extensions.Hosting) 9 | * [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) 10 | * [OpenCvSharp4](https://github.com/shimat/opencvsharp) 11 | * [System.Data.SQLite](https://system.data.sqlite.org/) 12 | * [ViewFaceCore](https://github.com/ViewFaceCore/ViewFaceCore) 13 | * [WpfAnimatedGif](https://github.com/XamlAnimatedGif/WpfAnimatedGif) 14 | * [Yitter.IdGenerator](https://github.com/yitter/idgenerator) -------------------------------------------------------------------------------- /docs/screenshot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/docs/screenshot.webp -------------------------------------------------------------------------------- /docs/screenshot1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/docs/screenshot1.webp -------------------------------------------------------------------------------- /docs/screenshot2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/docs/screenshot2.webp -------------------------------------------------------------------------------- /docs/screenshot3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/docs/screenshot3.webp -------------------------------------------------------------------------------- /docs/screenshot4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/docs/screenshot4.webp -------------------------------------------------------------------------------- /docs/screenshot5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/docs/screenshot5.webp -------------------------------------------------------------------------------- /test/test.smartlibrary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/test/test.smartlibrary --------------------------------------------------------------------------------