| @@ -0,0 +1,16 @@ | |||||
| | |||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | |||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Altob.NtuInvoiceGateway", "Altob.NtuInvoiceGateway\Altob.NtuInvoiceGateway.csproj", "{056E4823-4B80-4779-8C69-FB1C0A19F585}" | |||||
| EndProject | |||||
| Global | |||||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||||
| Debug|Any CPU = Debug|Any CPU | |||||
| Release|Any CPU = Release|Any CPU | |||||
| EndGlobalSection | |||||
| GlobalSection(ProjectConfigurationPlatforms) = postSolution | |||||
| {056E4823-4B80-4779-8C69-FB1C0A19F585}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
| {056E4823-4B80-4779-8C69-FB1C0A19F585}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
| {056E4823-4B80-4779-8C69-FB1C0A19F585}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
| {056E4823-4B80-4779-8C69-FB1C0A19F585}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
| EndGlobalSection | |||||
| EndGlobal | |||||
| @@ -0,0 +1,363 @@ | |||||
| # ---> VisualStudioCode | |||||
| .vscode/* | |||||
| !.vscode/settings.json | |||||
| !.vscode/tasks.json | |||||
| !.vscode/launch.json | |||||
| !.vscode/extensions.json | |||||
| *.code-workspace | |||||
| # User-specific files | |||||
| *.rsuser | |||||
| *.suo | |||||
| *.user | |||||
| *.userosscache | |||||
| *.sln.docstates | |||||
| # User-specific files (MonoDevelop/Xamarin Studio) | |||||
| *.userprefs | |||||
| # Build results | |||||
| [Dd]ebug/ | |||||
| [Dd]ebugPublic/ | |||||
| [Rr]elease/ | |||||
| [Rr]eleases/ | |||||
| x64/ | |||||
| x86/ | |||||
| [Aa][Rr][Mm]/ | |||||
| [Aa][Rr][Mm]64/ | |||||
| bld/ | |||||
| [Bb]in/ | |||||
| [Oo]bj/ | |||||
| [Ll]og/ | |||||
| # Visual Studio 2015/2017 cache/options directory | |||||
| .vs/ | |||||
| # Uncomment if you have tasks that create the project's static files in wwwroot | |||||
| #wwwroot/ | |||||
| # Visual Studio 2017 auto generated files | |||||
| Generated\ Files/ | |||||
| # MSTest test Results | |||||
| [Tt]est[Rr]esult*/ | |||||
| [Bb]uild[Ll]og.* | |||||
| # NUNIT | |||||
| *.VisualState.xml | |||||
| TestResult.xml | |||||
| # Build Results of an ATL Project | |||||
| [Dd]ebugPS/ | |||||
| [Rr]eleasePS/ | |||||
| dlldata.c | |||||
| # Benchmark Results | |||||
| BenchmarkDotNet.Artifacts/ | |||||
| # .NET Core | |||||
| project.lock.json | |||||
| project.fragment.lock.json | |||||
| artifacts/ | |||||
| # StyleCop | |||||
| StyleCopReport.xml | |||||
| # Files built by Visual Studio | |||||
| *_i.c | |||||
| *_p.c | |||||
| *_h.h | |||||
| *.ilk | |||||
| *.meta | |||||
| *.obj | |||||
| *.iobj | |||||
| *.pch | |||||
| *.pdb | |||||
| *.ipdb | |||||
| *.pgc | |||||
| *.pgd | |||||
| *.rsp | |||||
| *.sbr | |||||
| *.tlb | |||||
| *.tli | |||||
| *.tlh | |||||
| *.tmp | |||||
| *.tmp_proj | |||||
| *_wpftmp.csproj | |||||
| *.log | |||||
| *.vspscc | |||||
| *.vssscc | |||||
| .builds | |||||
| *.pidb | |||||
| *.svclog | |||||
| *.scc | |||||
| # Chutzpah Test files | |||||
| _Chutzpah* | |||||
| # Visual C++ cache files | |||||
| ipch/ | |||||
| *.aps | |||||
| *.ncb | |||||
| *.opendb | |||||
| *.opensdf | |||||
| *.sdf | |||||
| *.cachefile | |||||
| *.VC.db | |||||
| *.VC.VC.opendb | |||||
| # Visual Studio profiler | |||||
| *.psess | |||||
| *.vsp | |||||
| *.vspx | |||||
| *.sap | |||||
| # Visual Studio Trace Files | |||||
| *.e2e | |||||
| # TFS 2012 Local Workspace | |||||
| $tf/ | |||||
| # Guidance Automation Toolkit | |||||
| *.gpState | |||||
| # ReSharper is a .NET coding add-in | |||||
| _ReSharper*/ | |||||
| *.[Rr]e[Ss]harper | |||||
| *.DotSettings.user | |||||
| # JustCode is a .NET coding add-in | |||||
| .JustCode | |||||
| # TeamCity is a build add-in | |||||
| _TeamCity* | |||||
| # DotCover is a Code Coverage Tool | |||||
| *.dotCover | |||||
| # AxoCover is a Code Coverage Tool | |||||
| .axoCover/* | |||||
| !.axoCover/settings.json | |||||
| # Visual Studio code coverage results | |||||
| *.coverage | |||||
| *.coveragexml | |||||
| # NCrunch | |||||
| _NCrunch_* | |||||
| .*crunch*.local.xml | |||||
| nCrunchTemp_* | |||||
| # MightyMoose | |||||
| *.mm.* | |||||
| AutoTest.Net/ | |||||
| # Web workbench (sass) | |||||
| .sass-cache/ | |||||
| # Installshield output folder | |||||
| [Ee]xpress/ | |||||
| # DocProject is a documentation generator add-in | |||||
| DocProject/buildhelp/ | |||||
| DocProject/Help/*.HxT | |||||
| DocProject/Help/*.HxC | |||||
| DocProject/Help/*.hhc | |||||
| DocProject/Help/*.hhk | |||||
| DocProject/Help/*.hhp | |||||
| DocProject/Help/Html2 | |||||
| DocProject/Help/html | |||||
| # Click-Once directory | |||||
| publish/ | |||||
| # Publish Web Output | |||||
| *.[Pp]ublish.xml | |||||
| *.azurePubxml | |||||
| # Note: Comment the next line if you want to checkin your web deploy settings, | |||||
| # but database connection strings (with potential passwords) will be unencrypted | |||||
| *.publishproj | |||||
| # Microsoft Azure Web App publish settings. Comment the next line if you want to | |||||
| # checkin your Azure Web App publish settings, but sensitive information contained | |||||
| # in these scripts will be unencrypted | |||||
| PublishScripts/ | |||||
| # NuGet Packages | |||||
| *.nupkg | |||||
| # The packages folder can be ignored because of Package Restore | |||||
| **/[Pp]ackages/* | |||||
| # except build/, which is used as an MSBuild target. | |||||
| !**/[Pp]ackages/build/ | |||||
| # Uncomment if necessary however generally it will be regenerated when needed | |||||
| #!**/[Pp]ackages/repositories.config | |||||
| # NuGet v3's project.json files produces more ignorable files | |||||
| *.nuget.props | |||||
| *.nuget.targets | |||||
| # Microsoft Azure Build Output | |||||
| csx/ | |||||
| *.build.csdef | |||||
| # Microsoft Azure Emulator | |||||
| ecf/ | |||||
| rcf/ | |||||
| # Windows Store app package directories and files | |||||
| AppPackages/ | |||||
| BundleArtifacts/ | |||||
| Package.StoreAssociation.xml | |||||
| _pkginfo.txt | |||||
| *.appx | |||||
| # Visual Studio cache files | |||||
| # files ending in .cache can be ignored | |||||
| *.[Cc]ache | |||||
| # but keep track of directories ending in .cache | |||||
| !?*.[Cc]ache/ | |||||
| # Others | |||||
| ClientBin/ | |||||
| ~$* | |||||
| *~ | |||||
| *.dbmdl | |||||
| *.dbproj.schemaview | |||||
| *.jfm | |||||
| *.pfx | |||||
| *.publishsettings | |||||
| orleans.codegen.cs | |||||
| # Including strong name files can present a security risk | |||||
| # (https://github.com/github/gitignore/pull/2483#issue-259490424) | |||||
| #*.snk | |||||
| # Since there are multiple workflows, uncomment next line to ignore bower_components | |||||
| # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) | |||||
| #bower_components/ | |||||
| # RIA/Silverlight projects | |||||
| Generated_Code/ | |||||
| # Backup & report files from converting an old project file | |||||
| # to a newer Visual Studio version. Backup files are not needed, | |||||
| # because we have git ;-) | |||||
| _UpgradeReport_Files/ | |||||
| Backup*/ | |||||
| UpgradeLog*.XML | |||||
| UpgradeLog*.htm | |||||
| ServiceFabricBackup/ | |||||
| *.rptproj.bak | |||||
| # SQL Server files | |||||
| *.mdf | |||||
| *.ldf | |||||
| *.ndf | |||||
| # Business Intelligence projects | |||||
| *.rdl.data | |||||
| *.bim.layout | |||||
| *.bim_*.settings | |||||
| *.rptproj.rsuser | |||||
| *- Backup*.rdl | |||||
| # Microsoft Fakes | |||||
| FakesAssemblies/ | |||||
| # GhostDoc plugin setting file | |||||
| *.GhostDoc.xml | |||||
| # Node.js Tools for Visual Studio | |||||
| .ntvs_analysis.dat | |||||
| node_modules/ | |||||
| # Visual Studio 6 build log | |||||
| *.plg | |||||
| # Visual Studio 6 workspace options file | |||||
| *.opt | |||||
| # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) | |||||
| *.vbw | |||||
| # Visual Studio LightSwitch build output | |||||
| **/*.HTMLClient/GeneratedArtifacts | |||||
| **/*.DesktopClient/GeneratedArtifacts | |||||
| **/*.DesktopClient/ModelManifest.xml | |||||
| **/*.Server/GeneratedArtifacts | |||||
| **/*.Server/ModelManifest.xml | |||||
| _Pvt_Extensions | |||||
| # Paket dependency manager | |||||
| .paket/paket.exe | |||||
| paket-files/ | |||||
| # FAKE - F# Make | |||||
| .fake/ | |||||
| # JetBrains Rider | |||||
| .idea/ | |||||
| *.sln.iml | |||||
| # CodeRush personal settings | |||||
| .cr/personal | |||||
| # Python Tools for Visual Studio (PTVS) | |||||
| __pycache__/ | |||||
| *.pyc | |||||
| # Cake - Uncomment if you are using it | |||||
| # tools/** | |||||
| # !tools/packages.config | |||||
| # Tabs Studio | |||||
| *.tss | |||||
| # Telerik's JustMock configuration file | |||||
| *.jmconfig | |||||
| # BizTalk build output | |||||
| *.btp.cs | |||||
| *.btm.cs | |||||
| *.odx.cs | |||||
| *.xsd.cs | |||||
| # OpenCover UI analysis results | |||||
| OpenCover/ | |||||
| # Azure Stream Analytics local run output | |||||
| ASALocalRun/ | |||||
| # MSBuild Binary and Structured Log | |||||
| *.binlog | |||||
| # NVidia Nsight GPU debugger configuration file | |||||
| *.nvuser | |||||
| # MFractors (Xamarin productivity tool) working folder | |||||
| .mfractor/ | |||||
| # Local History for Visual Studio | |||||
| .localhistory/ | |||||
| # BeatPulse healthcheck temp database | |||||
| healthchecksdb | |||||
| # Other | |||||
| dotnet-tools.json | |||||
| /Tool/PaymentSupplierSettingScriptTool/PaymentSupplierSettingScriptTool/Properties/PublishProfiles | |||||
| /Altob.PaymentService | |||||
| /PaymentService.MGT/aspnet-core/src/PaymentService.MGT.Web.Host/App_Data/Logs/Logs.txt | |||||
| /PaymentService.Api | |||||
| /Altob.PaymentService.Api_B2B | |||||
| /Altob.PaymentService.Web_B2B | |||||
| /PaymentService.MGT/angular/dev | |||||
| /PaymentService.MGT/angular/dev.zip | |||||
| /PaymentService.MGT/PaymentService.MGT.Api | |||||
| /PaymentService.MGT/PaymentService.MGT.Api.zip | |||||
| /Altob.PaymentService.Schedule_B2B | |||||
| /PaymentService.MGT/angular/dist-b2b | |||||
| /PaymentService.MGT/TransactionService.MGT.Api | |||||
| /PaymentService.MGT/TransactionService.MGT.Api_B2B | |||||
| /.claude | |||||
| @@ -0,0 +1,9 @@ | |||||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | |||||
| <PropertyGroup> | |||||
| <TargetFramework>net9.0</TargetFramework> | |||||
| <Nullable>enable</Nullable> | |||||
| <ImplicitUsings>enable</ImplicitUsings> | |||||
| </PropertyGroup> | |||||
| </Project> | |||||
| @@ -0,0 +1,7 @@ | |||||
| namespace Altob.NtuInvoiceGateway.Models; | |||||
| public class CompanyInfo | |||||
| { | |||||
| public string Name { get; set; } = string.Empty; | |||||
| public string TaxId { get; set; } = string.Empty; | |||||
| } | |||||
| @@ -0,0 +1,53 @@ | |||||
| using System.ComponentModel.DataAnnotations; | |||||
| namespace Altob.NtuInvoiceGateway.Models; | |||||
| public class InvoiceRequest | |||||
| { | |||||
| [Required(ErrorMessage = "開發票者統編為必填")] | |||||
| [StringLength(8, ErrorMessage = "統編長度不能超過8個字元")] | |||||
| [RegularExpression(@"^\d{8}$", ErrorMessage = "統編格式不正確,必須為8位數字")] | |||||
| public string Identifier { get; set; } = string.Empty; | |||||
| [Required(ErrorMessage = "發票交易日期為必填")] | |||||
| public string TransDateTime { get; set; } = string.Empty; | |||||
| [Required(ErrorMessage = "發票交易金額為必填")] | |||||
| [StringLength(6, ErrorMessage = "金額長度不能超過6個字元")] | |||||
| public string TransAmount { get; set; } = string.Empty; | |||||
| [Required(ErrorMessage = "設備代號為必填")] | |||||
| [StringLength(16, ErrorMessage = "設備代號長度不能超過16個字元")] | |||||
| public string DeviceID { get; set; } = string.Empty; | |||||
| [StringLength(64, ErrorMessage = "Email長度不能超過64個字元")] | |||||
| [EmailAddress(ErrorMessage = "Email格式不正確")] | |||||
| public string? Email { get; set; } | |||||
| [StringLength(8, ErrorMessage = "手機條碼長度不能超過8個字元")] | |||||
| [RegularExpression(@"^\/[A-Z0-9.+\-]{7}$", ErrorMessage = "手機條碼格式不正確,必須為8碼,第一碼為/,其餘為大寫英數字、點、加號或減號")] | |||||
| public string? CarrierID { get; set; } | |||||
| [Required(ErrorMessage = "停車場站代號為必填")] | |||||
| [StringLength(1, ErrorMessage = "停車場站代號長度不能超過1個字元")] | |||||
| public string LocationID { get; set; } = string.Empty; | |||||
| [Required(ErrorMessage = "車牌號碼為必填")] | |||||
| [StringLength(8, ErrorMessage = "車牌號碼長度不能超過8個字元")] | |||||
| public string CarPlateNum { get; set; } = string.Empty; | |||||
| [Required(ErrorMessage = "訂單編號為必填")] | |||||
| [StringLength(64, ErrorMessage = "訂單編號長度不能超過64個字元")] | |||||
| public string OrderID { get; set; } = string.Empty; | |||||
| [StringLength(8, ErrorMessage = "購買者統編長度不能超過8個字元")] | |||||
| [RegularExpression(@"^\d{8}$", ErrorMessage = "統編格式不正確,必須為8位數字")] | |||||
| public string? BuyerIdentifier { get; set; } | |||||
| // [StringLength(7, ErrorMessage = "愛心碼長度不能超過7個字元")] | |||||
| // public string? LoveCode { get; set; } | |||||
| [Required(ErrorMessage = "稅別為必填")] | |||||
| [StringLength(1, ErrorMessage = "稅別長度不能超過1個字元")] | |||||
| public string TaxType { get; set; } = string.Empty; | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| namespace Altob.NtuInvoiceGateway.Models; | |||||
| public class InvoiceResponse | |||||
| { | |||||
| public string msg { get; set; } = string.Empty; | |||||
| public string msgCode { get; set; } = string.Empty; | |||||
| public object? data { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,26 @@ | |||||
| @page | |||||
| @model ErrorModel | |||||
| @{ | |||||
| ViewData["Title"] = "Error"; | |||||
| } | |||||
| <h1 class="text-danger">Error.</h1> | |||||
| <h2 class="text-danger">An error occurred while processing your request.</h2> | |||||
| @if (Model.ShowRequestId) | |||||
| { | |||||
| <p> | |||||
| <strong>Request ID:</strong> <code>@Model.RequestId</code> | |||||
| </p> | |||||
| } | |||||
| <h3>Development Mode</h3> | |||||
| <p> | |||||
| Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred. | |||||
| </p> | |||||
| <p> | |||||
| <strong>The Development environment shouldn't be enabled for deployed applications.</strong> | |||||
| It can result in displaying sensitive information from exceptions to end users. | |||||
| For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> | |||||
| and restarting the app. | |||||
| </p> | |||||
| @@ -0,0 +1,26 @@ | |||||
| using System.Diagnostics; | |||||
| using Microsoft.AspNetCore.Mvc; | |||||
| using Microsoft.AspNetCore.Mvc.RazorPages; | |||||
| namespace Altob.NtuInvoiceGateway.Pages; | |||||
| [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] | |||||
| [IgnoreAntiforgeryToken] | |||||
| public class ErrorModel : PageModel | |||||
| { | |||||
| public string? RequestId { get; set; } | |||||
| public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); | |||||
| private readonly ILogger<ErrorModel> _logger; | |||||
| public ErrorModel(ILogger<ErrorModel> logger) | |||||
| { | |||||
| _logger = logger; | |||||
| } | |||||
| public void OnGet() | |||||
| { | |||||
| RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| @page | |||||
| @model IndexModel | |||||
| @{ | |||||
| ViewData["Title"] = "Home page"; | |||||
| } | |||||
| <div class="text-center"> | |||||
| <h1 class="display-4">Welcome</h1> | |||||
| <p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p> | |||||
| </div> | |||||
| @@ -0,0 +1,18 @@ | |||||
| using Microsoft.AspNetCore.Mvc; | |||||
| using Microsoft.AspNetCore.Mvc.RazorPages; | |||||
| namespace Altob.NtuInvoiceGateway.Pages; | |||||
| public class IndexModel : PageModel | |||||
| { | |||||
| private readonly ILogger<IndexModel> _logger; | |||||
| public IndexModel(ILogger<IndexModel> logger) | |||||
| { | |||||
| _logger = logger; | |||||
| } | |||||
| public void OnGet() | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,136 @@ | |||||
| @page | |||||
| @model InvoiceModel | |||||
| @{ | |||||
| ViewData["Title"] = "消費者發票資訊填寫"; | |||||
| Layout = "_SimpleLayout"; | |||||
| } | |||||
| <div class="container mt-4"> | |||||
| <div class="row justify-content-center"> | |||||
| <div class="col-md-8"> | |||||
| <div class="alert alert-secondary text-center mb-3" role="alert"> | |||||
| <h5 class="mb-0">營業人:@Model.CompanyName(統編:@Model.CompanyTaxId)</h5> | |||||
| </div> | |||||
| @* @if (!string.IsNullOrEmpty(Model.DisplayTransDateTime) || !string.IsNullOrEmpty(Model.DisplayTransAmount)) *@ | |||||
| @* { *@ | |||||
| <div class="card mb-3 border-info"> | |||||
| <div class="card-body bg-light"> | |||||
| <div class="row"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label fw-bold">發票日期:</label> | |||||
| <span class="ms-2">@Model.DisplayTransDateTime</span> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label fw-bold">發票金額:</label> | |||||
| <span class="ms-2">NT$ @Model.DisplayTransAmount</span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| @* } *@ | |||||
| <div class="card shadow-sm"> | |||||
| <div class="card-header bg-primary text-white"> | |||||
| <h4 class="mb-0">消費者發票資訊填寫</h4> | |||||
| </div> | |||||
| <div class="card-body"> | |||||
| @if (!string.IsNullOrEmpty(Model.ErrorMessage)) | |||||
| { | |||||
| <div class="alert alert-danger alert-dismissible fade show" role="alert"> | |||||
| @Model.ErrorMessage | |||||
| <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | |||||
| </div> | |||||
| } | |||||
| @if (!string.IsNullOrEmpty(Model.SuccessMessage)) | |||||
| { | |||||
| <div class="alert alert-success alert-dismissible fade show" role="alert"> | |||||
| @Model.SuccessMessage | |||||
| <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | |||||
| </div> | |||||
| } | |||||
| <form method="post"> | |||||
| <div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div> | |||||
| <!-- 隱藏欄位:系統傳入的資料 --> | |||||
| <input type="hidden" asp-for="InvoiceData.Identifier" /> | |||||
| <input type="hidden" asp-for="InvoiceData.TransDateTime" /> | |||||
| <input type="hidden" asp-for="InvoiceData.TransAmount" /> | |||||
| <input type="hidden" asp-for="InvoiceData.DeviceID" /> | |||||
| <input type="hidden" asp-for="InvoiceData.LocationID" /> | |||||
| <input type="hidden" asp-for="InvoiceData.CarPlateNum" /> | |||||
| <input type="hidden" asp-for="InvoiceData.OrderID" /> | |||||
| <input type="hidden" asp-for="InvoiceData.TaxType" /> | |||||
| <div class="alert alert-info mb-4"> | |||||
| <strong>注意:</strong>以下三個選項只能填寫其中一個 | |||||
| </div> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label asp-for="InvoiceData.Email" class="form-label">消費者信箱</label> | |||||
| <input asp-for="InvoiceData.Email" class="form-control" type="email" maxlength="64" placeholder="example@email.com" /> | |||||
| <span asp-validation-for="InvoiceData.Email" class="text-danger"></span> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label asp-for="InvoiceData.CarrierID" class="form-label">手機條碼</label> | |||||
| <input asp-for="InvoiceData.CarrierID" class="form-control" maxlength="8" placeholder="/ABC1234" /> | |||||
| <small class="form-text text-muted">8碼,第1碼為/,例:/ABC1234</small> | |||||
| <span asp-validation-for="InvoiceData.CarrierID" class="text-danger"></span> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label asp-for="InvoiceData.BuyerIdentifier" class="form-label">購買者統編</label> | |||||
| <input asp-for="InvoiceData.BuyerIdentifier" class="form-control" maxlength="8" placeholder="12345678" /> | |||||
| <small class="form-text text-muted">8位數字</small> | |||||
| <span asp-validation-for="InvoiceData.BuyerIdentifier" class="text-danger"></span> | |||||
| </div> | |||||
| @* <div class="col-md-6"> | |||||
| <label asp-for="InvoiceData.LoveCode" class="form-label">愛心碼</label> | |||||
| <input asp-for="InvoiceData.LoveCode" class="form-control" maxlength="7" placeholder="123456" /> | |||||
| <small class="form-text text-muted">輸入為捐贈發票</small> | |||||
| <span asp-validation-for="InvoiceData.LoveCode" class="text-danger"></span> | |||||
| </div> *@ | |||||
| </div> | |||||
| <div class="d-grid gap-2 mt-4"> | |||||
| <button type="submit" class="btn btn-primary btn-lg">送出</button> | |||||
| </div> | |||||
| </form> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| @section Scripts { | |||||
| @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} | |||||
| <script> | |||||
| // 確保只有一個選項被填寫 | |||||
| document.addEventListener('DOMContentLoaded', function() { | |||||
| const exclusiveFields = [ | |||||
| document.querySelector('input[name="InvoiceData.Email"]'), | |||||
| document.querySelector('input[name="InvoiceData.CarrierID"]'), | |||||
| document.querySelector('input[name="InvoiceData.BuyerIdentifier"]') | |||||
| // document.querySelector('input[name="InvoiceData.LoveCode"]') | |||||
| ]; | |||||
| exclusiveFields.forEach(field => { | |||||
| if (field) { | |||||
| field.addEventListener('input', function() { | |||||
| if (this.value) { | |||||
| exclusiveFields.forEach(otherField => { | |||||
| if (otherField !== this) { | |||||
| otherField.value = ''; | |||||
| } | |||||
| }); | |||||
| } | |||||
| }); | |||||
| } | |||||
| }); | |||||
| }); | |||||
| </script> | |||||
| } | |||||
| @@ -0,0 +1,159 @@ | |||||
| using Microsoft.AspNetCore.Mvc; | |||||
| using Microsoft.AspNetCore.Mvc.RazorPages; | |||||
| using Microsoft.Extensions.Options; | |||||
| using Altob.NtuInvoiceGateway.Models; | |||||
| using System.Text; | |||||
| using System.Text.Json; | |||||
| namespace Altob.NtuInvoiceGateway.Pages; | |||||
| public class InvoiceModel : PageModel | |||||
| { | |||||
| private readonly ILogger<InvoiceModel> _logger; | |||||
| private readonly IHttpClientFactory _httpClientFactory; | |||||
| private readonly CompanyInfo _companyInfo; | |||||
| public InvoiceModel( | |||||
| ILogger<InvoiceModel> logger, | |||||
| IHttpClientFactory httpClientFactory, | |||||
| IOptions<CompanyInfo> companyInfo) | |||||
| { | |||||
| _logger = logger; | |||||
| _httpClientFactory = httpClientFactory; | |||||
| _companyInfo = companyInfo.Value; | |||||
| } | |||||
| [BindProperty] | |||||
| public InvoiceRequest InvoiceData { get; set; } = new(); | |||||
| public string? ErrorMessage { get; set; } | |||||
| public string? SuccessMessage { get; set; } | |||||
| public string CompanyName => _companyInfo.Name; | |||||
| public string CompanyTaxId => _companyInfo.TaxId; | |||||
| // 顯示用的發票資訊(唯讀) | |||||
| public string DisplayTransDateTime { get; set; } = string.Empty; | |||||
| public string DisplayTransAmount { get; set; } = string.Empty; | |||||
| public void OnGet( | |||||
| string? identifier = null, | |||||
| string? transDateTime = null, | |||||
| string? transAmount = null, | |||||
| string? deviceID = null, | |||||
| string? email = null, | |||||
| string? carrierID = null, | |||||
| string? locationID = null, | |||||
| string? carPlateNum = null, | |||||
| string? orderID = null, | |||||
| string? buyerIdentifier = null, | |||||
| // string? loveCode = null, | |||||
| string? taxType = null) | |||||
| { | |||||
| // 從查詢字串接收系統轉頁傳來的資料 | |||||
| if (!string.IsNullOrEmpty(identifier)) | |||||
| InvoiceData.Identifier = identifier; | |||||
| if (!string.IsNullOrEmpty(transDateTime)) | |||||
| { | |||||
| InvoiceData.TransDateTime = transDateTime; | |||||
| DisplayTransDateTime = transDateTime; | |||||
| } | |||||
| if (!string.IsNullOrEmpty(transAmount)) | |||||
| { | |||||
| InvoiceData.TransAmount = transAmount; | |||||
| DisplayTransAmount = transAmount; | |||||
| } | |||||
| if (!string.IsNullOrEmpty(deviceID)) | |||||
| InvoiceData.DeviceID = deviceID; | |||||
| if (!string.IsNullOrEmpty(email)) | |||||
| InvoiceData.Email = email; | |||||
| if (!string.IsNullOrEmpty(carrierID)) | |||||
| InvoiceData.CarrierID = carrierID; | |||||
| if (!string.IsNullOrEmpty(locationID)) | |||||
| InvoiceData.LocationID = locationID; | |||||
| if (!string.IsNullOrEmpty(carPlateNum)) | |||||
| InvoiceData.CarPlateNum = carPlateNum; | |||||
| if (!string.IsNullOrEmpty(orderID)) | |||||
| InvoiceData.OrderID = orderID; | |||||
| if (!string.IsNullOrEmpty(buyerIdentifier)) | |||||
| InvoiceData.BuyerIdentifier = buyerIdentifier; | |||||
| // if (!string.IsNullOrEmpty(loveCode)) | |||||
| // InvoiceData.LoveCode = loveCode; | |||||
| if (!string.IsNullOrEmpty(taxType)) | |||||
| InvoiceData.TaxType = taxType; | |||||
| } | |||||
| public async Task<IActionResult> OnPostAsync() | |||||
| { | |||||
| // 保留發票資訊顯示 | |||||
| DisplayTransDateTime = InvoiceData.TransDateTime; | |||||
| DisplayTransAmount = InvoiceData.TransAmount; | |||||
| // 判斷是否為初始轉頁(三個選項都沒填寫) | |||||
| bool isInitialRedirect = string.IsNullOrEmpty(InvoiceData.Email) && | |||||
| string.IsNullOrEmpty(InvoiceData.CarrierID) && | |||||
| string.IsNullOrEmpty(InvoiceData.BuyerIdentifier); | |||||
| // 如果是初始轉頁,直接顯示表單讓用戶填寫 | |||||
| if (isInitialRedirect) | |||||
| { | |||||
| return Page(); | |||||
| } | |||||
| if (!ModelState.IsValid) | |||||
| { | |||||
| return Page(); | |||||
| } | |||||
| // 驗證 Email, CarrierID, BuyerIdentifier 只能填寫其中一個 | |||||
| int filledCount = 0; | |||||
| if (!string.IsNullOrEmpty(InvoiceData.Email)) filledCount++; | |||||
| if (!string.IsNullOrEmpty(InvoiceData.CarrierID)) filledCount++; | |||||
| if (!string.IsNullOrEmpty(InvoiceData.BuyerIdentifier)) filledCount++; | |||||
| // if (!string.IsNullOrEmpty(InvoiceData.LoveCode)) filledCount++; | |||||
| if (filledCount != 1) | |||||
| { | |||||
| ErrorMessage = "Email、手機條碼、購買者統編只能填寫其中一個"; | |||||
| return Page(); | |||||
| } | |||||
| try | |||||
| { | |||||
| // 呼叫 TODO API(這裡使用規格中的範例 API) | |||||
| var httpClient = _httpClientFactory.CreateClient(); | |||||
| var jsonContent = JsonSerializer.Serialize(InvoiceData, new JsonSerializerOptions | |||||
| { | |||||
| PropertyNamingPolicy = JsonNamingPolicy.CamelCase | |||||
| }); | |||||
| var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); | |||||
| // TODO: 替換為實際的 API 端點 | |||||
| var response = await httpClient.PostAsync("YOUR_API_ENDPOINT_HERE", content); | |||||
| var responseContent = await response.Content.ReadAsStringAsync(); | |||||
| var apiResponse = JsonSerializer.Deserialize<InvoiceResponse>(responseContent, new JsonSerializerOptions | |||||
| { | |||||
| PropertyNamingPolicy = JsonNamingPolicy.CamelCase | |||||
| }); | |||||
| if (apiResponse?.msgCode == "0000") | |||||
| { | |||||
| SuccessMessage = "發票資訊提交成功!"; | |||||
| _logger.LogInformation("Invoice submitted successfully for OrderID: {OrderID}", InvoiceData.OrderID); | |||||
| } | |||||
| else | |||||
| { | |||||
| ErrorMessage = $"提交失敗:{apiResponse?.msg ?? "未知錯誤"}"; | |||||
| _logger.LogWarning("Invoice submission failed: {Message}", apiResponse?.msg); | |||||
| } | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| ErrorMessage = $"系統錯誤:{ex.Message}"; | |||||
| _logger.LogError(ex, "Error submitting invoice for OrderID: {OrderID}", InvoiceData.OrderID); | |||||
| } | |||||
| return Page(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| @page | |||||
| @model PrivacyModel | |||||
| @{ | |||||
| ViewData["Title"] = "Privacy Policy"; | |||||
| } | |||||
| <h1>@ViewData["Title"]</h1> | |||||
| <p>Use this page to detail your site's privacy policy.</p> | |||||
| @@ -0,0 +1,18 @@ | |||||
| using Microsoft.AspNetCore.Mvc; | |||||
| using Microsoft.AspNetCore.Mvc.RazorPages; | |||||
| namespace Altob.NtuInvoiceGateway.Pages; | |||||
| public class PrivacyModel : PageModel | |||||
| { | |||||
| private readonly ILogger<PrivacyModel> _logger; | |||||
| public PrivacyModel(ILogger<PrivacyModel> logger) | |||||
| { | |||||
| _logger = logger; | |||||
| } | |||||
| public void OnGet() | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,52 @@ | |||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="utf-8"/> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |||||
| <title>@ViewData["Title"] - Altob.NtuInvoiceGateway</title> | |||||
| <script type="importmap"></script> | |||||
| <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"/> | |||||
| <link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/> | |||||
| <link rel="stylesheet" href="~/Altob.NtuInvoiceGateway.styles.css" asp-append-version="true"/> | |||||
| </head> | |||||
| <body> | |||||
| <header> | |||||
| <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> | |||||
| <div class="container"> | |||||
| <a class="navbar-brand" asp-area="" asp-page="/Index">Altob.NtuInvoiceGateway</a> | |||||
| <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent" | |||||
| aria-expanded="false" aria-label="Toggle navigation"> | |||||
| <span class="navbar-toggler-icon"></span> | |||||
| </button> | |||||
| <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> | |||||
| <ul class="navbar-nav flex-grow-1"> | |||||
| <li class="nav-item"> | |||||
| <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a> | |||||
| </li> | |||||
| <li class="nav-item"> | |||||
| <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a> | |||||
| </li> | |||||
| </ul> | |||||
| </div> | |||||
| </div> | |||||
| </nav> | |||||
| </header> | |||||
| <div class="container"> | |||||
| <main role="main" class="pb-3"> | |||||
| @RenderBody() | |||||
| </main> | |||||
| </div> | |||||
| <footer class="border-top footer text-muted"> | |||||
| <div class="container"> | |||||
| © 2026 - Altob.NtuInvoiceGateway - <a asp-area="" asp-page="/Privacy">Privacy</a> | |||||
| </div> | |||||
| </footer> | |||||
| <script src="~/lib/jquery/dist/jquery.min.js"></script> | |||||
| <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> | |||||
| <script src="~/js/site.js" asp-append-version="true"></script> | |||||
| @await RenderSectionAsync("Scripts", required: false) | |||||
| </body> | |||||
| </html> | |||||
| @@ -0,0 +1,48 @@ | |||||
| /* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification | |||||
| for details on configuring this project to bundle and minify static web assets. */ | |||||
| a.navbar-brand { | |||||
| white-space: normal; | |||||
| text-align: center; | |||||
| word-break: break-all; | |||||
| } | |||||
| a { | |||||
| color: #0077cc; | |||||
| } | |||||
| .btn-primary { | |||||
| color: #fff; | |||||
| background-color: #1b6ec2; | |||||
| border-color: #1861ac; | |||||
| } | |||||
| .nav-pills .nav-link.active, .nav-pills .show > .nav-link { | |||||
| color: #fff; | |||||
| background-color: #1b6ec2; | |||||
| border-color: #1861ac; | |||||
| } | |||||
| .border-top { | |||||
| border-top: 1px solid #e5e5e5; | |||||
| } | |||||
| .border-bottom { | |||||
| border-bottom: 1px solid #e5e5e5; | |||||
| } | |||||
| .box-shadow { | |||||
| box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); | |||||
| } | |||||
| button.accept-policy { | |||||
| font-size: 1rem; | |||||
| line-height: inherit; | |||||
| } | |||||
| .footer { | |||||
| position: absolute; | |||||
| bottom: 0; | |||||
| width: 100%; | |||||
| white-space: nowrap; | |||||
| line-height: 60px; | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| <!DOCTYPE html> | |||||
| <html lang="zh-TW"> | |||||
| <head> | |||||
| <meta charset="utf-8"/> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |||||
| <title>@ViewData["Title"] - Altob.NtuInvoiceGateway</title> | |||||
| <script type="importmap"></script> | |||||
| <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"/> | |||||
| <link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/> | |||||
| <link rel="stylesheet" href="~/Altob.NtuInvoiceGateway.styles.css" asp-append-version="true"/> | |||||
| </head> | |||||
| <body> | |||||
| <div class="container"> | |||||
| <main role="main" class="pb-3"> | |||||
| @RenderBody() | |||||
| </main> | |||||
| </div> | |||||
| <footer class="border-top footer text-muted mt-5"> | |||||
| <div class="container text-center"> | |||||
| © 2026 - Altob.NtuInvoiceGateway | |||||
| </div> | |||||
| </footer> | |||||
| <script src="~/lib/jquery/dist/jquery.min.js"></script> | |||||
| <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> | |||||
| <script src="~/js/site.js" asp-append-version="true"></script> | |||||
| @await RenderSectionAsync("Scripts", required: false) | |||||
| </body> | |||||
| </html> | |||||
| @@ -0,0 +1,2 @@ | |||||
| <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script> | |||||
| <script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script> | |||||
| @@ -0,0 +1,131 @@ | |||||
| @page | |||||
| @{ | |||||
| ViewData["Title"] = "POST 轉頁測試"; | |||||
| Layout = null; | |||||
| } | |||||
| <!DOCTYPE html> | |||||
| <html lang="zh-TW"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||||
| <title>發票系統 POST 測試頁面</title> | |||||
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> | |||||
| </head> | |||||
| <body> | |||||
| <div class="container mt-5"> | |||||
| <div class="card"> | |||||
| <div class="card-header bg-success text-white"> | |||||
| <h3>發票系統 POST 轉頁測試</h3> | |||||
| </div> | |||||
| <div class="card-body"> | |||||
| <form method="POST" action="/Invoice"> | |||||
| @Html.AntiForgeryToken() | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">開發票者統編 (Identifier) *</label> | |||||
| <input type="text" class="form-control" name="InvoiceData.Identifier" value="12345678" required> | |||||
| <small class="text-muted">8位數字</small> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">發票交易日期 (TransDateTime) *</label> | |||||
| <input type="text" class="form-control" name="InvoiceData.TransDateTime" value="2024-01-15 14:30:00" required> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">發票交易金額 (TransAmount) *</label> | |||||
| <input type="text" class="form-control" name="InvoiceData.TransAmount" value="150" required> | |||||
| <small class="text-muted">最多6位數</small> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">設備代號 (DeviceID) *</label> | |||||
| <input type="text" class="form-control" name="InvoiceData.DeviceID" value="DEVICE001" required> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">停車場站代號 (LocationID) *</label> | |||||
| <input type="text" class="form-control" name="InvoiceData.LocationID" value="A" maxlength="1" required> | |||||
| <small class="text-muted">1個字元</small> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">車牌號碼 (CarPlateNum) *</label> | |||||
| <input type="text" class="form-control" name="InvoiceData.CarPlateNum" value="ABC1234" required> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">訂單編號 (OrderID) *</label> | |||||
| <input type="text" class="form-control" name="InvoiceData.OrderID" value="ORDER20240115001" required> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">稅別 (TaxType) *</label> | |||||
| <select class="form-control" name="InvoiceData.TaxType" required> | |||||
| <option value="1">應稅</option> | |||||
| <option value="2">零稅率</option> | |||||
| <option value="3">免稅</option> | |||||
| </select> | |||||
| <small class="text-muted">1個字元</small> | |||||
| </div> | |||||
| </div> | |||||
| <hr> | |||||
| <h5 class="text-muted">選填欄位(可預填,留空則讓用戶填寫)</h5> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">消費者信箱 (Email)</label> | |||||
| <input type="email" class="form-control" name="InvoiceData.Email" placeholder="留空讓用戶填寫"> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">手機條碼 (CarrierID)</label> | |||||
| <input type="text" class="form-control" name="InvoiceData.CarrierID" placeholder="留空讓用戶填寫"> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">購買者統編 (BuyerIdentifier)</label> | |||||
| <input type="text" class="form-control" name="InvoiceData.BuyerIdentifier" placeholder="留空讓用戶填寫"> | |||||
| </div> | |||||
| </div> | |||||
| <div class="d-grid gap-2"> | |||||
| <button type="submit" class="btn btn-success btn-lg"> | |||||
| POST 方式轉頁到發票頁面 | |||||
| </button> | |||||
| </div> | |||||
| </form> | |||||
| <hr class="my-4"> | |||||
| <div class="alert alert-info"> | |||||
| <h5>POST 測試說明</h5> | |||||
| <ul> | |||||
| <li>所有必填欄位都會 POST 到發票頁面</li> | |||||
| <li>Email、手機條碼、購買者統編如果留空,將在發票頁面讓用戶填寫</li> | |||||
| <li>如果預填了這三個欄位的其中一個,用戶將直接看到該預填值</li> | |||||
| <li>此頁面包含有效的防偽令牌(CSRF Token)</li> | |||||
| </ul> | |||||
| </div> | |||||
| <hr> | |||||
| <div class="card bg-light"> | |||||
| <div class="card-body"> | |||||
| <h6>比較:GET vs POST</h6> | |||||
| <a href="/TestInvoice.html" class="btn btn-outline-primary btn-sm">GET 測試頁面</a> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> | |||||
| </body> | |||||
| </html> | |||||
| @@ -0,0 +1,10 @@ | |||||
| using Microsoft.AspNetCore.Mvc.RazorPages; | |||||
| namespace Altob.NtuInvoiceGateway.Pages; | |||||
| public class TestPostInvoiceModel : PageModel | |||||
| { | |||||
| public void OnGet() | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,3 @@ | |||||
| @using Altob.NtuInvoiceGateway | |||||
| @namespace Altob.NtuInvoiceGateway.Pages | |||||
| @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers | |||||
| @@ -0,0 +1,3 @@ | |||||
| @{ | |||||
| Layout = "_Layout"; | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| var builder = WebApplication.CreateBuilder(args); | |||||
| // Add services to the container. | |||||
| builder.Services.AddRazorPages(); | |||||
| builder.Services.AddHttpClient(); | |||||
| // Configure company info | |||||
| builder.Services.Configure<Altob.NtuInvoiceGateway.Models.CompanyInfo>( | |||||
| builder.Configuration.GetSection("CompanyInfo")); | |||||
| var app = builder.Build(); | |||||
| // Configure the HTTP request pipeline. | |||||
| if (!app.Environment.IsDevelopment()) | |||||
| { | |||||
| app.UseExceptionHandler("/Error"); | |||||
| // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. | |||||
| app.UseHsts(); | |||||
| } | |||||
| app.UseHttpsRedirection(); | |||||
| app.UseRouting(); | |||||
| app.UseAuthorization(); | |||||
| app.MapStaticAssets(); | |||||
| app.MapRazorPages() | |||||
| .WithStaticAssets(); | |||||
| app.Run(); | |||||
| @@ -0,0 +1,25 @@ | |||||
| { | |||||
| "$schema": "https://json.schemastore.org/launchsettings.json", | |||||
| "profiles": { | |||||
| "http": { | |||||
| "commandName": "Project", | |||||
| "dotnetRunMessages": true, | |||||
| "launchBrowser": true, | |||||
| "launchUrl": "TestIndex.html", | |||||
| "applicationUrl": "http://localhost:5103", | |||||
| "environmentVariables": { | |||||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||||
| } | |||||
| }, | |||||
| "https": { | |||||
| "commandName": "Project", | |||||
| "dotnetRunMessages": true, | |||||
| "launchBrowser": true, | |||||
| "launchUrl": "TestIndex.html", | |||||
| "applicationUrl": "https://localhost:7294;http://localhost:5103", | |||||
| "environmentVariables": { | |||||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| { | |||||
| "DetailedErrors": true, | |||||
| "Logging": { | |||||
| "LogLevel": { | |||||
| "Default": "Information", | |||||
| "Microsoft.AspNetCore": "Warning" | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| { | |||||
| "Logging": { | |||||
| "LogLevel": { | |||||
| "Default": "Information", | |||||
| "Microsoft.AspNetCore": "Warning" | |||||
| } | |||||
| }, | |||||
| "AllowedHosts": "*", | |||||
| "CompanyInfo": { | |||||
| "Name": "國立台灣大學臨時停車場", | |||||
| "TaxId": "18384226" | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,124 @@ | |||||
| 發票相關API規格書 | |||||
| 停⾞場商須依照規範開發相關發票功能 | |||||
| 1.線上開發票⾴⾯ | |||||
| Api路徑:廠商⾃訂後提供 | |||||
| Method:POST | |||||
| 資料格式:Json | |||||
| 備註:線上發票需注意 需要寄信通知客⼾發票開立 如果非⼿機載具歸⼾需寄送中獎通知信件 另外請⽤ | |||||
| OrderID對應通知時所使⽤的發票號碼開立 | |||||
| 請求範例: | |||||
| { | |||||
| "Identifier": "12345678", | |||||
| "TransDateTime": "yyyy/MM/dd HH:mm:ss", | |||||
| "TransAmount": "50", | |||||
| "DeviceID": "Parking@Intella", | |||||
| "Email": "email@gmail.com", | |||||
| "CarrierID": "/-91-.L2", | |||||
| "LocationID": "ArearId", | |||||
| "CarPlateNum": "1234AB", | |||||
| "OrderID": "orderId123", | |||||
| "BuyerIdentifier": "12345678", | |||||
| "LoveCode":"", | |||||
| "TaxType": "3" | |||||
| } | |||||
| 請求參數說明: | |||||
| 參數名稱 說明 是否必須 類型(⻑度上限) | |||||
| Identifier 開發票者統編 true string(8) | |||||
| TransDateTime 發票交易⽇期(yyyy/MM/dd HH:mm:ss) true string(20) | |||||
| TransAmount 發票交易⾦額 true string(6) | |||||
| DeviceID 設備代號 true string(16) | |||||
| LocationID 停⾞場站代號,各廠域對應代號 true string(1) | |||||
| CarPlateNum 完整⾞牌 (不包含 - 號,EX: ABC1234) true string(8) | |||||
| OrderID 訂單編號查詢繳費時產⽣的訂單編號 true string(64) | |||||
| Email 消費者信箱 optional string(64) | |||||
| CarrierID 消費者載具 optional string(8) | |||||
| BuyerIdentifier 購買者統編 optional string(8) | |||||
| TaxType 稅別3為免稅,免稅不給統編 true string(1) | |||||
| LoveCode 愛⼼碼 輸入為捐贈發票 optional string(7) | |||||
| Email, CarrierID, BuyerIdentifier, LoveCode 則⼀填寫 | |||||
| 回應參數: | |||||
| { | |||||
| "msg": "", | |||||
| "msgCode": "0000", | |||||
| "data": null | |||||
| } | |||||
| 回應參數說明: | |||||
| 參數名稱 說明 類型(⻑度上限) | |||||
| msg 如果失敗時填入失敗原因 string(64) | |||||
| msgCode 狀態碼(0000為成功,非0000為失敗) string(4) | |||||
| data 回應資料 object | |||||
| 2.發票資料回傳 | |||||
| Api路徑:https://mgt.intella.co/mgt-server/invoice/api/invoiceInfoSave | |||||
| Method:POST | |||||
| 資料格式:JSON | |||||
| 請求範例: | |||||
| { | |||||
| "OrderID": "orderId123", | |||||
| "BuyerIdentifier": "12345678" | "", | |||||
| "TransDateTime": "2024-09-12 09:09:12", | |||||
| "DeviceID": "Parking@Intella", | |||||
| "FreeTaxSalesAmount": "0", | |||||
| "InvoiceStatus": "1", | |||||
| "MainRemark": "備註", | |||||
| "Number": "TS94539547", | |||||
| "TaxableSalesAmount": "0", | |||||
| "TaxAmount": "0", | |||||
| "TotalAmount": "50", | |||||
| "LoveCode":"", | |||||
| "ZeroTaxSalesAmount": "0" | |||||
| } | |||||
| 請求參數說明: | |||||
| 參數名稱 說明 是否必 類型(⻑度 | |||||
| 須 上限) | |||||
| OrderID 訂單編號 true string(64) | |||||
| BuyerIdentifier 購買者統編 false string(8) | |||||
| TransDateTime 發票⽇期(yyyy-MM-dd HH:mm:ss) true string(20) | |||||
| DeviceID 發票機號 true string(16) | |||||
| FreeTaxSalesAmount 免稅額 true string(6) | |||||
| 發票狀態(1=開立成功 , 2=已作廢 , 其他皆 | |||||
| InvoiceStatus 為開立失敗) true string(1) | |||||
| 發票備註 | |||||
| MainRemark 發票號碼 true string(64) | |||||
| Number 未含稅⾦額 true string(11) | |||||
| TaxableSalesAmount 稅額 true string(6) | |||||
| TaxAmount 銷售額 true string(6) | |||||
| TotalAmount 零稅額 true string(6) | |||||
| ZeroTaxSalesAmount 愛⼼碼 輸入為捐贈發票 true string(6) | |||||
| LoveCode optional string(7) | |||||
| 回應參數: | |||||
| { | |||||
| "msg": "", | |||||
| "msgCode": "0000", | |||||
| "data": null | |||||
| } | |||||
| 回應參數說明: | |||||
| 參數名稱 說明 類型(⻑度上限) | |||||
| msg 如果失敗時填入失敗原因 string(64) | |||||
| msgCode 狀態碼(0000為成功,非0000為失敗) string(4) | |||||
| data 回應資料 object | |||||
| @@ -0,0 +1,147 @@ | |||||
| <!DOCTYPE html> | |||||
| <html lang="zh-TW"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||||
| <title>發票系統測試入口</title> | |||||
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> | |||||
| <style> | |||||
| .test-card { | |||||
| transition: transform 0.2s; | |||||
| cursor: pointer; | |||||
| } | |||||
| .test-card:hover { | |||||
| transform: translateY(-5px); | |||||
| box-shadow: 0 4px 20px rgba(0,0,0,0.2); | |||||
| } | |||||
| .hero-section { | |||||
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |||||
| color: white; | |||||
| padding: 60px 0; | |||||
| margin-bottom: 40px; | |||||
| } | |||||
| </style> | |||||
| </head> | |||||
| <body> | |||||
| <div class="hero-section"> | |||||
| <div class="container text-center"> | |||||
| <h1 class="display-4 mb-3">發票系統測試入口</h1> | |||||
| <p class="lead">選擇測試方式來測試發票頁面轉頁功能</p> | |||||
| </div> | |||||
| </div> | |||||
| <div class="container"> | |||||
| <div class="row justify-content-center"> | |||||
| <div class="col-md-5 mb-4"> | |||||
| <a href="/TestInvoice.html" class="text-decoration-none"> | |||||
| <div class="card test-card border-primary h-100"> | |||||
| <div class="card-header bg-primary text-white text-center"> | |||||
| <h3 class="mb-0"> | |||||
| <i class="bi bi-link-45deg"></i> GET 測試 | |||||
| </h3> | |||||
| </div> | |||||
| <div class="card-body"> | |||||
| <h5 class="card-title text-primary">查詢字串方式轉頁</h5> | |||||
| <p class="card-text">使用 URL 查詢字串(Query String)傳遞參數到發票頁面</p> | |||||
| <hr> | |||||
| <h6 class="text-muted">特點:</h6> | |||||
| <ul> | |||||
| <li>參數顯示在 URL 中</li> | |||||
| <li>可以直接複製連結</li> | |||||
| <li>適合簡單的頁面跳轉</li> | |||||
| <li>支援書籤和分享</li> | |||||
| </ul> | |||||
| <div class="alert alert-info mt-3"> | |||||
| <small><strong>範例:</strong><br>/Invoice?identifier=12345678&transAmount=150...</small> | |||||
| </div> | |||||
| </div> | |||||
| <div class="card-footer text-center bg-light"> | |||||
| <button class="btn btn-primary btn-lg w-100"> | |||||
| 進入 GET 測試頁面 | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| </a> | |||||
| </div> | |||||
| <div class="col-md-5 mb-4"> | |||||
| <a href="/TestPostInvoice" class="text-decoration-none"> | |||||
| <div class="card test-card border-success h-100"> | |||||
| <div class="card-header bg-success text-white text-center"> | |||||
| <h3 class="mb-0"> | |||||
| <i class="bi bi-envelope"></i> POST 測試 | |||||
| </h3> | |||||
| </div> | |||||
| <div class="card-body"> | |||||
| <h5 class="card-title text-success">表單提交方式轉頁</h5> | |||||
| <p class="card-text">使用 HTTP POST 方法傳遞參數到發票頁面</p> | |||||
| <hr> | |||||
| <h6 class="text-muted">特點:</h6> | |||||
| <ul> | |||||
| <li>參數不顯示在 URL 中</li> | |||||
| <li>更安全的資料傳輸</li> | |||||
| <li>適合敏感資料傳遞</li> | |||||
| <li>包含 CSRF 防護</li> | |||||
| </ul> | |||||
| <div class="alert alert-success mt-3"> | |||||
| <small><strong>推薦用於:</strong><br>生產環境的系統整合</small> | |||||
| </div> | |||||
| </div> | |||||
| <div class="card-footer text-center bg-light"> | |||||
| <button class="btn btn-success btn-lg w-100"> | |||||
| 進入 POST 測試頁面 | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| </a> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row mt-4"> | |||||
| <div class="col-12"> | |||||
| <div class="card border-warning"> | |||||
| <div class="card-header bg-warning"> | |||||
| <h5 class="mb-0">測試說明</h5> | |||||
| </div> | |||||
| <div class="card-body"> | |||||
| <div class="row"> | |||||
| <div class="col-md-6"> | |||||
| <h6>必填參數(兩種方式都需要):</h6> | |||||
| <ul> | |||||
| <li>開發票者統編 (identifier)</li> | |||||
| <li>發票交易日期 (transDateTime)</li> | |||||
| <li>發票交易金額 (transAmount)</li> | |||||
| <li>設備代號 (deviceID)</li> | |||||
| <li>停車場站代號 (locationID)</li> | |||||
| <li>車牌號碼 (carPlateNum)</li> | |||||
| <li>訂單編號 (orderID)</li> | |||||
| <li>稅別 (taxType)</li> | |||||
| </ul> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <h6>選填參數(擇一填寫):</h6> | |||||
| <ul> | |||||
| <li>消費者信箱 (email)</li> | |||||
| <li>手機條碼 (carrierID)</li> | |||||
| <li>購買者統編 (buyerIdentifier)</li> | |||||
| </ul> | |||||
| <div class="alert alert-warning mt-2"> | |||||
| <small><strong>注意:</strong>選填參數可以預填,也可以留空讓用戶在發票頁面填寫</small> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="text-center mt-4 mb-5"> | |||||
| <p class="text-muted"> | |||||
| <small>© 2024 發票系統測試工具 | Altob NTU Invoice Gateway</small> | |||||
| </p> | |||||
| </div> | |||||
| </div> | |||||
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> | |||||
| </body> | |||||
| </html> | |||||
| @@ -0,0 +1,120 @@ | |||||
| <!DOCTYPE html> | |||||
| <html lang="zh-TW"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||||
| <title>發票系統測試頁面</title> | |||||
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> | |||||
| </head> | |||||
| <body> | |||||
| <div class="container mt-5"> | |||||
| <div class="card"> | |||||
| <div class="card-header bg-info text-white"> | |||||
| <h3>發票系統轉頁測試</h3> | |||||
| </div> | |||||
| <div class="card-body"> | |||||
| <form id="testForm" method="GET" action="/Invoice"> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">開發票者統編 (identifier) *</label> | |||||
| <input type="text" class="form-control" name="identifier" value="12345678" required> | |||||
| <small class="text-muted">8位數字</small> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">發票交易日期 (transDateTime) *</label> | |||||
| <input type="text" class="form-control" name="transDateTime" value="2024-01-15 14:30:00" required> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">發票交易金額 (transAmount) *</label> | |||||
| <input type="text" class="form-control" name="transAmount" value="150" required> | |||||
| <small class="text-muted">最多6位數</small> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">設備代號 (deviceID) *</label> | |||||
| <input type="text" class="form-control" name="deviceID" value="DEVICE001" required> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">停車場站代號 (locationID) *</label> | |||||
| <input type="text" class="form-control" name="locationID" value="A" maxlength="1" required> | |||||
| <small class="text-muted">1個字元</small> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">車牌號碼 (carPlateNum) *</label> | |||||
| <input type="text" class="form-control" name="carPlateNum" value="ABC1234" required> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">訂單編號 (orderID) *</label> | |||||
| <input type="text" class="form-control" name="orderID" value="ORDER20240115001" required> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">稅別 (taxType) *</label> | |||||
| <select class="form-control" name="taxType" required> | |||||
| <option value="1">應稅</option> | |||||
| <option value="2">零稅率</option> | |||||
| <option value="3">免稅</option> | |||||
| </select> | |||||
| <small class="text-muted">1個字元</small> | |||||
| </div> | |||||
| </div> | |||||
| <hr> | |||||
| <h5>選填欄位(可預填)</h5> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">消費者信箱 (email)</label> | |||||
| <input type="email" class="form-control" name="email" placeholder="user@example.com"> | |||||
| </div> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">手機條碼 (carrierID)</label> | |||||
| <input type="text" class="form-control" name="carrierID" placeholder="/ABC1234"> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-md-6"> | |||||
| <label class="form-label">購買者統編 (buyerIdentifier)</label> | |||||
| <input type="text" class="form-control" name="buyerIdentifier" placeholder="87654321"> | |||||
| </div> | |||||
| </div> | |||||
| <div class="d-grid gap-2"> | |||||
| <button type="submit" class="btn btn-primary btn-lg"> | |||||
| GET 方式轉頁到發票頁面 | |||||
| </button> | |||||
| </div> | |||||
| </form> | |||||
| <hr class="my-4"> | |||||
| <h5>快速測試連結</h5> | |||||
| <div class="d-grid gap-2"> | |||||
| <a href="/Invoice?identifier=12345678&transDateTime=2024-01-15%2014:30:00&transAmount=150&deviceID=DEVICE001&locationID=A&carPlateNum=ABC1234&orderID=ORDER001&taxType=1" | |||||
| class="btn btn-outline-secondary"> | |||||
| 測試連結 1 - 只有必填欄位 | |||||
| </a> | |||||
| <a href="/Invoice?identifier=12345678&transDateTime=2024-01-15%2014:30:00&transAmount=150&deviceID=DEVICE001&locationID=A&carPlateNum=ABC1234&orderID=ORDER002&taxType=1&email=test@example.com" | |||||
| class="btn btn-outline-secondary"> | |||||
| 測試連結 2 - 預填 Email | |||||
| </a> | |||||
| <a href="/Invoice?identifier=12345678&transDateTime=2024-01-15%2014:30:00&transAmount=150&deviceID=DEVICE001&locationID=A&carPlateNum=ABC1234&orderID=ORDER003&taxType=1&carrierID=/ABC1234" | |||||
| class="btn btn-outline-secondary"> | |||||
| 測試連結 3 - 預填手機條碼 | |||||
| </a> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> | |||||
| </body> | |||||
| </html> | |||||
| @@ -0,0 +1,31 @@ | |||||
| html { | |||||
| font-size: 14px; | |||||
| } | |||||
| @media (min-width: 768px) { | |||||
| html { | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { | |||||
| box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; | |||||
| } | |||||
| html { | |||||
| position: relative; | |||||
| min-height: 100%; | |||||
| } | |||||
| body { | |||||
| margin-bottom: 60px; | |||||
| } | |||||
| .form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { | |||||
| color: var(--bs-secondary-color); | |||||
| text-align: end; | |||||
| } | |||||
| .form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { | |||||
| text-align: start; | |||||
| } | |||||
| @@ -0,0 +1,4 @@ | |||||
| // Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification | |||||
| // for details on configuring this project to bundle and minify static web assets. | |||||
| // Write your JavaScript code. | |||||
| @@ -0,0 +1,22 @@ | |||||
| The MIT License (MIT) | |||||
| Copyright (c) 2011-2021 Twitter, Inc. | |||||
| Copyright (c) 2011-2021 The Bootstrap Authors | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in | |||||
| all copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
| THE SOFTWARE. | |||||
| @@ -0,0 +1,597 @@ | |||||
| /*! | |||||
| * Bootstrap Reboot v5.3.3 (https://getbootstrap.com/) | |||||
| * Copyright 2011-2024 The Bootstrap Authors | |||||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) | |||||
| */ | |||||
| :root, | |||||
| [data-bs-theme=light] { | |||||
| --bs-blue: #0d6efd; | |||||
| --bs-indigo: #6610f2; | |||||
| --bs-purple: #6f42c1; | |||||
| --bs-pink: #d63384; | |||||
| --bs-red: #dc3545; | |||||
| --bs-orange: #fd7e14; | |||||
| --bs-yellow: #ffc107; | |||||
| --bs-green: #198754; | |||||
| --bs-teal: #20c997; | |||||
| --bs-cyan: #0dcaf0; | |||||
| --bs-black: #000; | |||||
| --bs-white: #fff; | |||||
| --bs-gray: #6c757d; | |||||
| --bs-gray-dark: #343a40; | |||||
| --bs-gray-100: #f8f9fa; | |||||
| --bs-gray-200: #e9ecef; | |||||
| --bs-gray-300: #dee2e6; | |||||
| --bs-gray-400: #ced4da; | |||||
| --bs-gray-500: #adb5bd; | |||||
| --bs-gray-600: #6c757d; | |||||
| --bs-gray-700: #495057; | |||||
| --bs-gray-800: #343a40; | |||||
| --bs-gray-900: #212529; | |||||
| --bs-primary: #0d6efd; | |||||
| --bs-secondary: #6c757d; | |||||
| --bs-success: #198754; | |||||
| --bs-info: #0dcaf0; | |||||
| --bs-warning: #ffc107; | |||||
| --bs-danger: #dc3545; | |||||
| --bs-light: #f8f9fa; | |||||
| --bs-dark: #212529; | |||||
| --bs-primary-rgb: 13, 110, 253; | |||||
| --bs-secondary-rgb: 108, 117, 125; | |||||
| --bs-success-rgb: 25, 135, 84; | |||||
| --bs-info-rgb: 13, 202, 240; | |||||
| --bs-warning-rgb: 255, 193, 7; | |||||
| --bs-danger-rgb: 220, 53, 69; | |||||
| --bs-light-rgb: 248, 249, 250; | |||||
| --bs-dark-rgb: 33, 37, 41; | |||||
| --bs-primary-text-emphasis: #052c65; | |||||
| --bs-secondary-text-emphasis: #2b2f32; | |||||
| --bs-success-text-emphasis: #0a3622; | |||||
| --bs-info-text-emphasis: #055160; | |||||
| --bs-warning-text-emphasis: #664d03; | |||||
| --bs-danger-text-emphasis: #58151c; | |||||
| --bs-light-text-emphasis: #495057; | |||||
| --bs-dark-text-emphasis: #495057; | |||||
| --bs-primary-bg-subtle: #cfe2ff; | |||||
| --bs-secondary-bg-subtle: #e2e3e5; | |||||
| --bs-success-bg-subtle: #d1e7dd; | |||||
| --bs-info-bg-subtle: #cff4fc; | |||||
| --bs-warning-bg-subtle: #fff3cd; | |||||
| --bs-danger-bg-subtle: #f8d7da; | |||||
| --bs-light-bg-subtle: #fcfcfd; | |||||
| --bs-dark-bg-subtle: #ced4da; | |||||
| --bs-primary-border-subtle: #9ec5fe; | |||||
| --bs-secondary-border-subtle: #c4c8cb; | |||||
| --bs-success-border-subtle: #a3cfbb; | |||||
| --bs-info-border-subtle: #9eeaf9; | |||||
| --bs-warning-border-subtle: #ffe69c; | |||||
| --bs-danger-border-subtle: #f1aeb5; | |||||
| --bs-light-border-subtle: #e9ecef; | |||||
| --bs-dark-border-subtle: #adb5bd; | |||||
| --bs-white-rgb: 255, 255, 255; | |||||
| --bs-black-rgb: 0, 0, 0; | |||||
| --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; | |||||
| --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | |||||
| --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); | |||||
| --bs-body-font-family: var(--bs-font-sans-serif); | |||||
| --bs-body-font-size: 1rem; | |||||
| --bs-body-font-weight: 400; | |||||
| --bs-body-line-height: 1.5; | |||||
| --bs-body-color: #212529; | |||||
| --bs-body-color-rgb: 33, 37, 41; | |||||
| --bs-body-bg: #fff; | |||||
| --bs-body-bg-rgb: 255, 255, 255; | |||||
| --bs-emphasis-color: #000; | |||||
| --bs-emphasis-color-rgb: 0, 0, 0; | |||||
| --bs-secondary-color: rgba(33, 37, 41, 0.75); | |||||
| --bs-secondary-color-rgb: 33, 37, 41; | |||||
| --bs-secondary-bg: #e9ecef; | |||||
| --bs-secondary-bg-rgb: 233, 236, 239; | |||||
| --bs-tertiary-color: rgba(33, 37, 41, 0.5); | |||||
| --bs-tertiary-color-rgb: 33, 37, 41; | |||||
| --bs-tertiary-bg: #f8f9fa; | |||||
| --bs-tertiary-bg-rgb: 248, 249, 250; | |||||
| --bs-heading-color: inherit; | |||||
| --bs-link-color: #0d6efd; | |||||
| --bs-link-color-rgb: 13, 110, 253; | |||||
| --bs-link-decoration: underline; | |||||
| --bs-link-hover-color: #0a58ca; | |||||
| --bs-link-hover-color-rgb: 10, 88, 202; | |||||
| --bs-code-color: #d63384; | |||||
| --bs-highlight-color: #212529; | |||||
| --bs-highlight-bg: #fff3cd; | |||||
| --bs-border-width: 1px; | |||||
| --bs-border-style: solid; | |||||
| --bs-border-color: #dee2e6; | |||||
| --bs-border-color-translucent: rgba(0, 0, 0, 0.175); | |||||
| --bs-border-radius: 0.375rem; | |||||
| --bs-border-radius-sm: 0.25rem; | |||||
| --bs-border-radius-lg: 0.5rem; | |||||
| --bs-border-radius-xl: 1rem; | |||||
| --bs-border-radius-xxl: 2rem; | |||||
| --bs-border-radius-2xl: var(--bs-border-radius-xxl); | |||||
| --bs-border-radius-pill: 50rem; | |||||
| --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); | |||||
| --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); | |||||
| --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); | |||||
| --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); | |||||
| --bs-focus-ring-width: 0.25rem; | |||||
| --bs-focus-ring-opacity: 0.25; | |||||
| --bs-focus-ring-color: rgba(13, 110, 253, 0.25); | |||||
| --bs-form-valid-color: #198754; | |||||
| --bs-form-valid-border-color: #198754; | |||||
| --bs-form-invalid-color: #dc3545; | |||||
| --bs-form-invalid-border-color: #dc3545; | |||||
| } | |||||
| [data-bs-theme=dark] { | |||||
| color-scheme: dark; | |||||
| --bs-body-color: #dee2e6; | |||||
| --bs-body-color-rgb: 222, 226, 230; | |||||
| --bs-body-bg: #212529; | |||||
| --bs-body-bg-rgb: 33, 37, 41; | |||||
| --bs-emphasis-color: #fff; | |||||
| --bs-emphasis-color-rgb: 255, 255, 255; | |||||
| --bs-secondary-color: rgba(222, 226, 230, 0.75); | |||||
| --bs-secondary-color-rgb: 222, 226, 230; | |||||
| --bs-secondary-bg: #343a40; | |||||
| --bs-secondary-bg-rgb: 52, 58, 64; | |||||
| --bs-tertiary-color: rgba(222, 226, 230, 0.5); | |||||
| --bs-tertiary-color-rgb: 222, 226, 230; | |||||
| --bs-tertiary-bg: #2b3035; | |||||
| --bs-tertiary-bg-rgb: 43, 48, 53; | |||||
| --bs-primary-text-emphasis: #6ea8fe; | |||||
| --bs-secondary-text-emphasis: #a7acb1; | |||||
| --bs-success-text-emphasis: #75b798; | |||||
| --bs-info-text-emphasis: #6edff6; | |||||
| --bs-warning-text-emphasis: #ffda6a; | |||||
| --bs-danger-text-emphasis: #ea868f; | |||||
| --bs-light-text-emphasis: #f8f9fa; | |||||
| --bs-dark-text-emphasis: #dee2e6; | |||||
| --bs-primary-bg-subtle: #031633; | |||||
| --bs-secondary-bg-subtle: #161719; | |||||
| --bs-success-bg-subtle: #051b11; | |||||
| --bs-info-bg-subtle: #032830; | |||||
| --bs-warning-bg-subtle: #332701; | |||||
| --bs-danger-bg-subtle: #2c0b0e; | |||||
| --bs-light-bg-subtle: #343a40; | |||||
| --bs-dark-bg-subtle: #1a1d20; | |||||
| --bs-primary-border-subtle: #084298; | |||||
| --bs-secondary-border-subtle: #41464b; | |||||
| --bs-success-border-subtle: #0f5132; | |||||
| --bs-info-border-subtle: #087990; | |||||
| --bs-warning-border-subtle: #997404; | |||||
| --bs-danger-border-subtle: #842029; | |||||
| --bs-light-border-subtle: #495057; | |||||
| --bs-dark-border-subtle: #343a40; | |||||
| --bs-heading-color: inherit; | |||||
| --bs-link-color: #6ea8fe; | |||||
| --bs-link-hover-color: #8bb9fe; | |||||
| --bs-link-color-rgb: 110, 168, 254; | |||||
| --bs-link-hover-color-rgb: 139, 185, 254; | |||||
| --bs-code-color: #e685b5; | |||||
| --bs-highlight-color: #dee2e6; | |||||
| --bs-highlight-bg: #664d03; | |||||
| --bs-border-color: #495057; | |||||
| --bs-border-color-translucent: rgba(255, 255, 255, 0.15); | |||||
| --bs-form-valid-color: #75b798; | |||||
| --bs-form-valid-border-color: #75b798; | |||||
| --bs-form-invalid-color: #ea868f; | |||||
| --bs-form-invalid-border-color: #ea868f; | |||||
| } | |||||
| *, | |||||
| *::before, | |||||
| *::after { | |||||
| box-sizing: border-box; | |||||
| } | |||||
| @media (prefers-reduced-motion: no-preference) { | |||||
| :root { | |||||
| scroll-behavior: smooth; | |||||
| } | |||||
| } | |||||
| body { | |||||
| margin: 0; | |||||
| font-family: var(--bs-body-font-family); | |||||
| font-size: var(--bs-body-font-size); | |||||
| font-weight: var(--bs-body-font-weight); | |||||
| line-height: var(--bs-body-line-height); | |||||
| color: var(--bs-body-color); | |||||
| text-align: var(--bs-body-text-align); | |||||
| background-color: var(--bs-body-bg); | |||||
| -webkit-text-size-adjust: 100%; | |||||
| -webkit-tap-highlight-color: rgba(0, 0, 0, 0); | |||||
| } | |||||
| hr { | |||||
| margin: 1rem 0; | |||||
| color: inherit; | |||||
| border: 0; | |||||
| border-top: var(--bs-border-width) solid; | |||||
| opacity: 0.25; | |||||
| } | |||||
| h6, h5, h4, h3, h2, h1 { | |||||
| margin-top: 0; | |||||
| margin-bottom: 0.5rem; | |||||
| font-weight: 500; | |||||
| line-height: 1.2; | |||||
| color: var(--bs-heading-color); | |||||
| } | |||||
| h1 { | |||||
| font-size: calc(1.375rem + 1.5vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h1 { | |||||
| font-size: 2.5rem; | |||||
| } | |||||
| } | |||||
| h2 { | |||||
| font-size: calc(1.325rem + 0.9vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h2 { | |||||
| font-size: 2rem; | |||||
| } | |||||
| } | |||||
| h3 { | |||||
| font-size: calc(1.3rem + 0.6vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h3 { | |||||
| font-size: 1.75rem; | |||||
| } | |||||
| } | |||||
| h4 { | |||||
| font-size: calc(1.275rem + 0.3vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h4 { | |||||
| font-size: 1.5rem; | |||||
| } | |||||
| } | |||||
| h5 { | |||||
| font-size: 1.25rem; | |||||
| } | |||||
| h6 { | |||||
| font-size: 1rem; | |||||
| } | |||||
| p { | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| } | |||||
| abbr[title] { | |||||
| -webkit-text-decoration: underline dotted; | |||||
| text-decoration: underline dotted; | |||||
| cursor: help; | |||||
| -webkit-text-decoration-skip-ink: none; | |||||
| text-decoration-skip-ink: none; | |||||
| } | |||||
| address { | |||||
| margin-bottom: 1rem; | |||||
| font-style: normal; | |||||
| line-height: inherit; | |||||
| } | |||||
| ol, | |||||
| ul { | |||||
| padding-left: 2rem; | |||||
| } | |||||
| ol, | |||||
| ul, | |||||
| dl { | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| } | |||||
| ol ol, | |||||
| ul ul, | |||||
| ol ul, | |||||
| ul ol { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| dt { | |||||
| font-weight: 700; | |||||
| } | |||||
| dd { | |||||
| margin-bottom: 0.5rem; | |||||
| margin-left: 0; | |||||
| } | |||||
| blockquote { | |||||
| margin: 0 0 1rem; | |||||
| } | |||||
| b, | |||||
| strong { | |||||
| font-weight: bolder; | |||||
| } | |||||
| small { | |||||
| font-size: 0.875em; | |||||
| } | |||||
| mark { | |||||
| padding: 0.1875em; | |||||
| color: var(--bs-highlight-color); | |||||
| background-color: var(--bs-highlight-bg); | |||||
| } | |||||
| sub, | |||||
| sup { | |||||
| position: relative; | |||||
| font-size: 0.75em; | |||||
| line-height: 0; | |||||
| vertical-align: baseline; | |||||
| } | |||||
| sub { | |||||
| bottom: -0.25em; | |||||
| } | |||||
| sup { | |||||
| top: -0.5em; | |||||
| } | |||||
| a { | |||||
| color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); | |||||
| text-decoration: underline; | |||||
| } | |||||
| a:hover { | |||||
| --bs-link-color-rgb: var(--bs-link-hover-color-rgb); | |||||
| } | |||||
| a:not([href]):not([class]), a:not([href]):not([class]):hover { | |||||
| color: inherit; | |||||
| text-decoration: none; | |||||
| } | |||||
| pre, | |||||
| code, | |||||
| kbd, | |||||
| samp { | |||||
| font-family: var(--bs-font-monospace); | |||||
| font-size: 1em; | |||||
| } | |||||
| pre { | |||||
| display: block; | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| overflow: auto; | |||||
| font-size: 0.875em; | |||||
| } | |||||
| pre code { | |||||
| font-size: inherit; | |||||
| color: inherit; | |||||
| word-break: normal; | |||||
| } | |||||
| code { | |||||
| font-size: 0.875em; | |||||
| color: var(--bs-code-color); | |||||
| word-wrap: break-word; | |||||
| } | |||||
| a > code { | |||||
| color: inherit; | |||||
| } | |||||
| kbd { | |||||
| padding: 0.1875rem 0.375rem; | |||||
| font-size: 0.875em; | |||||
| color: var(--bs-body-bg); | |||||
| background-color: var(--bs-body-color); | |||||
| border-radius: 0.25rem; | |||||
| } | |||||
| kbd kbd { | |||||
| padding: 0; | |||||
| font-size: 1em; | |||||
| } | |||||
| figure { | |||||
| margin: 0 0 1rem; | |||||
| } | |||||
| img, | |||||
| svg { | |||||
| vertical-align: middle; | |||||
| } | |||||
| table { | |||||
| caption-side: bottom; | |||||
| border-collapse: collapse; | |||||
| } | |||||
| caption { | |||||
| padding-top: 0.5rem; | |||||
| padding-bottom: 0.5rem; | |||||
| color: var(--bs-secondary-color); | |||||
| text-align: left; | |||||
| } | |||||
| th { | |||||
| text-align: inherit; | |||||
| text-align: -webkit-match-parent; | |||||
| } | |||||
| thead, | |||||
| tbody, | |||||
| tfoot, | |||||
| tr, | |||||
| td, | |||||
| th { | |||||
| border-color: inherit; | |||||
| border-style: solid; | |||||
| border-width: 0; | |||||
| } | |||||
| label { | |||||
| display: inline-block; | |||||
| } | |||||
| button { | |||||
| border-radius: 0; | |||||
| } | |||||
| button:focus:not(:focus-visible) { | |||||
| outline: 0; | |||||
| } | |||||
| input, | |||||
| button, | |||||
| select, | |||||
| optgroup, | |||||
| textarea { | |||||
| margin: 0; | |||||
| font-family: inherit; | |||||
| font-size: inherit; | |||||
| line-height: inherit; | |||||
| } | |||||
| button, | |||||
| select { | |||||
| text-transform: none; | |||||
| } | |||||
| [role=button] { | |||||
| cursor: pointer; | |||||
| } | |||||
| select { | |||||
| word-wrap: normal; | |||||
| } | |||||
| select:disabled { | |||||
| opacity: 1; | |||||
| } | |||||
| [list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator { | |||||
| display: none !important; | |||||
| } | |||||
| button, | |||||
| [type=button], | |||||
| [type=reset], | |||||
| [type=submit] { | |||||
| -webkit-appearance: button; | |||||
| } | |||||
| button:not(:disabled), | |||||
| [type=button]:not(:disabled), | |||||
| [type=reset]:not(:disabled), | |||||
| [type=submit]:not(:disabled) { | |||||
| cursor: pointer; | |||||
| } | |||||
| ::-moz-focus-inner { | |||||
| padding: 0; | |||||
| border-style: none; | |||||
| } | |||||
| textarea { | |||||
| resize: vertical; | |||||
| } | |||||
| fieldset { | |||||
| min-width: 0; | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| border: 0; | |||||
| } | |||||
| legend { | |||||
| float: left; | |||||
| width: 100%; | |||||
| padding: 0; | |||||
| margin-bottom: 0.5rem; | |||||
| font-size: calc(1.275rem + 0.3vw); | |||||
| line-height: inherit; | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| legend { | |||||
| font-size: 1.5rem; | |||||
| } | |||||
| } | |||||
| legend + * { | |||||
| clear: left; | |||||
| } | |||||
| ::-webkit-datetime-edit-fields-wrapper, | |||||
| ::-webkit-datetime-edit-text, | |||||
| ::-webkit-datetime-edit-minute, | |||||
| ::-webkit-datetime-edit-hour-field, | |||||
| ::-webkit-datetime-edit-day-field, | |||||
| ::-webkit-datetime-edit-month-field, | |||||
| ::-webkit-datetime-edit-year-field { | |||||
| padding: 0; | |||||
| } | |||||
| ::-webkit-inner-spin-button { | |||||
| height: auto; | |||||
| } | |||||
| [type=search] { | |||||
| -webkit-appearance: textfield; | |||||
| outline-offset: -2px; | |||||
| } | |||||
| /* rtl:raw: | |||||
| [type="tel"], | |||||
| [type="url"], | |||||
| [type="email"], | |||||
| [type="number"] { | |||||
| direction: ltr; | |||||
| } | |||||
| */ | |||||
| ::-webkit-search-decoration { | |||||
| -webkit-appearance: none; | |||||
| } | |||||
| ::-webkit-color-swatch-wrapper { | |||||
| padding: 0; | |||||
| } | |||||
| ::-webkit-file-upload-button { | |||||
| font: inherit; | |||||
| -webkit-appearance: button; | |||||
| } | |||||
| ::file-selector-button { | |||||
| font: inherit; | |||||
| -webkit-appearance: button; | |||||
| } | |||||
| output { | |||||
| display: inline-block; | |||||
| } | |||||
| iframe { | |||||
| border: 0; | |||||
| } | |||||
| summary { | |||||
| display: list-item; | |||||
| cursor: pointer; | |||||
| } | |||||
| progress { | |||||
| vertical-align: baseline; | |||||
| } | |||||
| [hidden] { | |||||
| display: none !important; | |||||
| } | |||||
| /*# sourceMappingURL=bootstrap-reboot.css.map */ | |||||
| @@ -0,0 +1,594 @@ | |||||
| /*! | |||||
| * Bootstrap Reboot v5.3.3 (https://getbootstrap.com/) | |||||
| * Copyright 2011-2024 The Bootstrap Authors | |||||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) | |||||
| */ | |||||
| :root, | |||||
| [data-bs-theme=light] { | |||||
| --bs-blue: #0d6efd; | |||||
| --bs-indigo: #6610f2; | |||||
| --bs-purple: #6f42c1; | |||||
| --bs-pink: #d63384; | |||||
| --bs-red: #dc3545; | |||||
| --bs-orange: #fd7e14; | |||||
| --bs-yellow: #ffc107; | |||||
| --bs-green: #198754; | |||||
| --bs-teal: #20c997; | |||||
| --bs-cyan: #0dcaf0; | |||||
| --bs-black: #000; | |||||
| --bs-white: #fff; | |||||
| --bs-gray: #6c757d; | |||||
| --bs-gray-dark: #343a40; | |||||
| --bs-gray-100: #f8f9fa; | |||||
| --bs-gray-200: #e9ecef; | |||||
| --bs-gray-300: #dee2e6; | |||||
| --bs-gray-400: #ced4da; | |||||
| --bs-gray-500: #adb5bd; | |||||
| --bs-gray-600: #6c757d; | |||||
| --bs-gray-700: #495057; | |||||
| --bs-gray-800: #343a40; | |||||
| --bs-gray-900: #212529; | |||||
| --bs-primary: #0d6efd; | |||||
| --bs-secondary: #6c757d; | |||||
| --bs-success: #198754; | |||||
| --bs-info: #0dcaf0; | |||||
| --bs-warning: #ffc107; | |||||
| --bs-danger: #dc3545; | |||||
| --bs-light: #f8f9fa; | |||||
| --bs-dark: #212529; | |||||
| --bs-primary-rgb: 13, 110, 253; | |||||
| --bs-secondary-rgb: 108, 117, 125; | |||||
| --bs-success-rgb: 25, 135, 84; | |||||
| --bs-info-rgb: 13, 202, 240; | |||||
| --bs-warning-rgb: 255, 193, 7; | |||||
| --bs-danger-rgb: 220, 53, 69; | |||||
| --bs-light-rgb: 248, 249, 250; | |||||
| --bs-dark-rgb: 33, 37, 41; | |||||
| --bs-primary-text-emphasis: #052c65; | |||||
| --bs-secondary-text-emphasis: #2b2f32; | |||||
| --bs-success-text-emphasis: #0a3622; | |||||
| --bs-info-text-emphasis: #055160; | |||||
| --bs-warning-text-emphasis: #664d03; | |||||
| --bs-danger-text-emphasis: #58151c; | |||||
| --bs-light-text-emphasis: #495057; | |||||
| --bs-dark-text-emphasis: #495057; | |||||
| --bs-primary-bg-subtle: #cfe2ff; | |||||
| --bs-secondary-bg-subtle: #e2e3e5; | |||||
| --bs-success-bg-subtle: #d1e7dd; | |||||
| --bs-info-bg-subtle: #cff4fc; | |||||
| --bs-warning-bg-subtle: #fff3cd; | |||||
| --bs-danger-bg-subtle: #f8d7da; | |||||
| --bs-light-bg-subtle: #fcfcfd; | |||||
| --bs-dark-bg-subtle: #ced4da; | |||||
| --bs-primary-border-subtle: #9ec5fe; | |||||
| --bs-secondary-border-subtle: #c4c8cb; | |||||
| --bs-success-border-subtle: #a3cfbb; | |||||
| --bs-info-border-subtle: #9eeaf9; | |||||
| --bs-warning-border-subtle: #ffe69c; | |||||
| --bs-danger-border-subtle: #f1aeb5; | |||||
| --bs-light-border-subtle: #e9ecef; | |||||
| --bs-dark-border-subtle: #adb5bd; | |||||
| --bs-white-rgb: 255, 255, 255; | |||||
| --bs-black-rgb: 0, 0, 0; | |||||
| --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; | |||||
| --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | |||||
| --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); | |||||
| --bs-body-font-family: var(--bs-font-sans-serif); | |||||
| --bs-body-font-size: 1rem; | |||||
| --bs-body-font-weight: 400; | |||||
| --bs-body-line-height: 1.5; | |||||
| --bs-body-color: #212529; | |||||
| --bs-body-color-rgb: 33, 37, 41; | |||||
| --bs-body-bg: #fff; | |||||
| --bs-body-bg-rgb: 255, 255, 255; | |||||
| --bs-emphasis-color: #000; | |||||
| --bs-emphasis-color-rgb: 0, 0, 0; | |||||
| --bs-secondary-color: rgba(33, 37, 41, 0.75); | |||||
| --bs-secondary-color-rgb: 33, 37, 41; | |||||
| --bs-secondary-bg: #e9ecef; | |||||
| --bs-secondary-bg-rgb: 233, 236, 239; | |||||
| --bs-tertiary-color: rgba(33, 37, 41, 0.5); | |||||
| --bs-tertiary-color-rgb: 33, 37, 41; | |||||
| --bs-tertiary-bg: #f8f9fa; | |||||
| --bs-tertiary-bg-rgb: 248, 249, 250; | |||||
| --bs-heading-color: inherit; | |||||
| --bs-link-color: #0d6efd; | |||||
| --bs-link-color-rgb: 13, 110, 253; | |||||
| --bs-link-decoration: underline; | |||||
| --bs-link-hover-color: #0a58ca; | |||||
| --bs-link-hover-color-rgb: 10, 88, 202; | |||||
| --bs-code-color: #d63384; | |||||
| --bs-highlight-color: #212529; | |||||
| --bs-highlight-bg: #fff3cd; | |||||
| --bs-border-width: 1px; | |||||
| --bs-border-style: solid; | |||||
| --bs-border-color: #dee2e6; | |||||
| --bs-border-color-translucent: rgba(0, 0, 0, 0.175); | |||||
| --bs-border-radius: 0.375rem; | |||||
| --bs-border-radius-sm: 0.25rem; | |||||
| --bs-border-radius-lg: 0.5rem; | |||||
| --bs-border-radius-xl: 1rem; | |||||
| --bs-border-radius-xxl: 2rem; | |||||
| --bs-border-radius-2xl: var(--bs-border-radius-xxl); | |||||
| --bs-border-radius-pill: 50rem; | |||||
| --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); | |||||
| --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); | |||||
| --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); | |||||
| --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); | |||||
| --bs-focus-ring-width: 0.25rem; | |||||
| --bs-focus-ring-opacity: 0.25; | |||||
| --bs-focus-ring-color: rgba(13, 110, 253, 0.25); | |||||
| --bs-form-valid-color: #198754; | |||||
| --bs-form-valid-border-color: #198754; | |||||
| --bs-form-invalid-color: #dc3545; | |||||
| --bs-form-invalid-border-color: #dc3545; | |||||
| } | |||||
| [data-bs-theme=dark] { | |||||
| color-scheme: dark; | |||||
| --bs-body-color: #dee2e6; | |||||
| --bs-body-color-rgb: 222, 226, 230; | |||||
| --bs-body-bg: #212529; | |||||
| --bs-body-bg-rgb: 33, 37, 41; | |||||
| --bs-emphasis-color: #fff; | |||||
| --bs-emphasis-color-rgb: 255, 255, 255; | |||||
| --bs-secondary-color: rgba(222, 226, 230, 0.75); | |||||
| --bs-secondary-color-rgb: 222, 226, 230; | |||||
| --bs-secondary-bg: #343a40; | |||||
| --bs-secondary-bg-rgb: 52, 58, 64; | |||||
| --bs-tertiary-color: rgba(222, 226, 230, 0.5); | |||||
| --bs-tertiary-color-rgb: 222, 226, 230; | |||||
| --bs-tertiary-bg: #2b3035; | |||||
| --bs-tertiary-bg-rgb: 43, 48, 53; | |||||
| --bs-primary-text-emphasis: #6ea8fe; | |||||
| --bs-secondary-text-emphasis: #a7acb1; | |||||
| --bs-success-text-emphasis: #75b798; | |||||
| --bs-info-text-emphasis: #6edff6; | |||||
| --bs-warning-text-emphasis: #ffda6a; | |||||
| --bs-danger-text-emphasis: #ea868f; | |||||
| --bs-light-text-emphasis: #f8f9fa; | |||||
| --bs-dark-text-emphasis: #dee2e6; | |||||
| --bs-primary-bg-subtle: #031633; | |||||
| --bs-secondary-bg-subtle: #161719; | |||||
| --bs-success-bg-subtle: #051b11; | |||||
| --bs-info-bg-subtle: #032830; | |||||
| --bs-warning-bg-subtle: #332701; | |||||
| --bs-danger-bg-subtle: #2c0b0e; | |||||
| --bs-light-bg-subtle: #343a40; | |||||
| --bs-dark-bg-subtle: #1a1d20; | |||||
| --bs-primary-border-subtle: #084298; | |||||
| --bs-secondary-border-subtle: #41464b; | |||||
| --bs-success-border-subtle: #0f5132; | |||||
| --bs-info-border-subtle: #087990; | |||||
| --bs-warning-border-subtle: #997404; | |||||
| --bs-danger-border-subtle: #842029; | |||||
| --bs-light-border-subtle: #495057; | |||||
| --bs-dark-border-subtle: #343a40; | |||||
| --bs-heading-color: inherit; | |||||
| --bs-link-color: #6ea8fe; | |||||
| --bs-link-hover-color: #8bb9fe; | |||||
| --bs-link-color-rgb: 110, 168, 254; | |||||
| --bs-link-hover-color-rgb: 139, 185, 254; | |||||
| --bs-code-color: #e685b5; | |||||
| --bs-highlight-color: #dee2e6; | |||||
| --bs-highlight-bg: #664d03; | |||||
| --bs-border-color: #495057; | |||||
| --bs-border-color-translucent: rgba(255, 255, 255, 0.15); | |||||
| --bs-form-valid-color: #75b798; | |||||
| --bs-form-valid-border-color: #75b798; | |||||
| --bs-form-invalid-color: #ea868f; | |||||
| --bs-form-invalid-border-color: #ea868f; | |||||
| } | |||||
| *, | |||||
| *::before, | |||||
| *::after { | |||||
| box-sizing: border-box; | |||||
| } | |||||
| @media (prefers-reduced-motion: no-preference) { | |||||
| :root { | |||||
| scroll-behavior: smooth; | |||||
| } | |||||
| } | |||||
| body { | |||||
| margin: 0; | |||||
| font-family: var(--bs-body-font-family); | |||||
| font-size: var(--bs-body-font-size); | |||||
| font-weight: var(--bs-body-font-weight); | |||||
| line-height: var(--bs-body-line-height); | |||||
| color: var(--bs-body-color); | |||||
| text-align: var(--bs-body-text-align); | |||||
| background-color: var(--bs-body-bg); | |||||
| -webkit-text-size-adjust: 100%; | |||||
| -webkit-tap-highlight-color: rgba(0, 0, 0, 0); | |||||
| } | |||||
| hr { | |||||
| margin: 1rem 0; | |||||
| color: inherit; | |||||
| border: 0; | |||||
| border-top: var(--bs-border-width) solid; | |||||
| opacity: 0.25; | |||||
| } | |||||
| h6, h5, h4, h3, h2, h1 { | |||||
| margin-top: 0; | |||||
| margin-bottom: 0.5rem; | |||||
| font-weight: 500; | |||||
| line-height: 1.2; | |||||
| color: var(--bs-heading-color); | |||||
| } | |||||
| h1 { | |||||
| font-size: calc(1.375rem + 1.5vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h1 { | |||||
| font-size: 2.5rem; | |||||
| } | |||||
| } | |||||
| h2 { | |||||
| font-size: calc(1.325rem + 0.9vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h2 { | |||||
| font-size: 2rem; | |||||
| } | |||||
| } | |||||
| h3 { | |||||
| font-size: calc(1.3rem + 0.6vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h3 { | |||||
| font-size: 1.75rem; | |||||
| } | |||||
| } | |||||
| h4 { | |||||
| font-size: calc(1.275rem + 0.3vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h4 { | |||||
| font-size: 1.5rem; | |||||
| } | |||||
| } | |||||
| h5 { | |||||
| font-size: 1.25rem; | |||||
| } | |||||
| h6 { | |||||
| font-size: 1rem; | |||||
| } | |||||
| p { | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| } | |||||
| abbr[title] { | |||||
| -webkit-text-decoration: underline dotted; | |||||
| text-decoration: underline dotted; | |||||
| cursor: help; | |||||
| -webkit-text-decoration-skip-ink: none; | |||||
| text-decoration-skip-ink: none; | |||||
| } | |||||
| address { | |||||
| margin-bottom: 1rem; | |||||
| font-style: normal; | |||||
| line-height: inherit; | |||||
| } | |||||
| ol, | |||||
| ul { | |||||
| padding-right: 2rem; | |||||
| } | |||||
| ol, | |||||
| ul, | |||||
| dl { | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| } | |||||
| ol ol, | |||||
| ul ul, | |||||
| ol ul, | |||||
| ul ol { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| dt { | |||||
| font-weight: 700; | |||||
| } | |||||
| dd { | |||||
| margin-bottom: 0.5rem; | |||||
| margin-right: 0; | |||||
| } | |||||
| blockquote { | |||||
| margin: 0 0 1rem; | |||||
| } | |||||
| b, | |||||
| strong { | |||||
| font-weight: bolder; | |||||
| } | |||||
| small { | |||||
| font-size: 0.875em; | |||||
| } | |||||
| mark { | |||||
| padding: 0.1875em; | |||||
| color: var(--bs-highlight-color); | |||||
| background-color: var(--bs-highlight-bg); | |||||
| } | |||||
| sub, | |||||
| sup { | |||||
| position: relative; | |||||
| font-size: 0.75em; | |||||
| line-height: 0; | |||||
| vertical-align: baseline; | |||||
| } | |||||
| sub { | |||||
| bottom: -0.25em; | |||||
| } | |||||
| sup { | |||||
| top: -0.5em; | |||||
| } | |||||
| a { | |||||
| color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); | |||||
| text-decoration: underline; | |||||
| } | |||||
| a:hover { | |||||
| --bs-link-color-rgb: var(--bs-link-hover-color-rgb); | |||||
| } | |||||
| a:not([href]):not([class]), a:not([href]):not([class]):hover { | |||||
| color: inherit; | |||||
| text-decoration: none; | |||||
| } | |||||
| pre, | |||||
| code, | |||||
| kbd, | |||||
| samp { | |||||
| font-family: var(--bs-font-monospace); | |||||
| font-size: 1em; | |||||
| } | |||||
| pre { | |||||
| display: block; | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| overflow: auto; | |||||
| font-size: 0.875em; | |||||
| } | |||||
| pre code { | |||||
| font-size: inherit; | |||||
| color: inherit; | |||||
| word-break: normal; | |||||
| } | |||||
| code { | |||||
| font-size: 0.875em; | |||||
| color: var(--bs-code-color); | |||||
| word-wrap: break-word; | |||||
| } | |||||
| a > code { | |||||
| color: inherit; | |||||
| } | |||||
| kbd { | |||||
| padding: 0.1875rem 0.375rem; | |||||
| font-size: 0.875em; | |||||
| color: var(--bs-body-bg); | |||||
| background-color: var(--bs-body-color); | |||||
| border-radius: 0.25rem; | |||||
| } | |||||
| kbd kbd { | |||||
| padding: 0; | |||||
| font-size: 1em; | |||||
| } | |||||
| figure { | |||||
| margin: 0 0 1rem; | |||||
| } | |||||
| img, | |||||
| svg { | |||||
| vertical-align: middle; | |||||
| } | |||||
| table { | |||||
| caption-side: bottom; | |||||
| border-collapse: collapse; | |||||
| } | |||||
| caption { | |||||
| padding-top: 0.5rem; | |||||
| padding-bottom: 0.5rem; | |||||
| color: var(--bs-secondary-color); | |||||
| text-align: right; | |||||
| } | |||||
| th { | |||||
| text-align: inherit; | |||||
| text-align: -webkit-match-parent; | |||||
| } | |||||
| thead, | |||||
| tbody, | |||||
| tfoot, | |||||
| tr, | |||||
| td, | |||||
| th { | |||||
| border-color: inherit; | |||||
| border-style: solid; | |||||
| border-width: 0; | |||||
| } | |||||
| label { | |||||
| display: inline-block; | |||||
| } | |||||
| button { | |||||
| border-radius: 0; | |||||
| } | |||||
| button:focus:not(:focus-visible) { | |||||
| outline: 0; | |||||
| } | |||||
| input, | |||||
| button, | |||||
| select, | |||||
| optgroup, | |||||
| textarea { | |||||
| margin: 0; | |||||
| font-family: inherit; | |||||
| font-size: inherit; | |||||
| line-height: inherit; | |||||
| } | |||||
| button, | |||||
| select { | |||||
| text-transform: none; | |||||
| } | |||||
| [role=button] { | |||||
| cursor: pointer; | |||||
| } | |||||
| select { | |||||
| word-wrap: normal; | |||||
| } | |||||
| select:disabled { | |||||
| opacity: 1; | |||||
| } | |||||
| [list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator { | |||||
| display: none !important; | |||||
| } | |||||
| button, | |||||
| [type=button], | |||||
| [type=reset], | |||||
| [type=submit] { | |||||
| -webkit-appearance: button; | |||||
| } | |||||
| button:not(:disabled), | |||||
| [type=button]:not(:disabled), | |||||
| [type=reset]:not(:disabled), | |||||
| [type=submit]:not(:disabled) { | |||||
| cursor: pointer; | |||||
| } | |||||
| ::-moz-focus-inner { | |||||
| padding: 0; | |||||
| border-style: none; | |||||
| } | |||||
| textarea { | |||||
| resize: vertical; | |||||
| } | |||||
| fieldset { | |||||
| min-width: 0; | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| border: 0; | |||||
| } | |||||
| legend { | |||||
| float: right; | |||||
| width: 100%; | |||||
| padding: 0; | |||||
| margin-bottom: 0.5rem; | |||||
| font-size: calc(1.275rem + 0.3vw); | |||||
| line-height: inherit; | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| legend { | |||||
| font-size: 1.5rem; | |||||
| } | |||||
| } | |||||
| legend + * { | |||||
| clear: right; | |||||
| } | |||||
| ::-webkit-datetime-edit-fields-wrapper, | |||||
| ::-webkit-datetime-edit-text, | |||||
| ::-webkit-datetime-edit-minute, | |||||
| ::-webkit-datetime-edit-hour-field, | |||||
| ::-webkit-datetime-edit-day-field, | |||||
| ::-webkit-datetime-edit-month-field, | |||||
| ::-webkit-datetime-edit-year-field { | |||||
| padding: 0; | |||||
| } | |||||
| ::-webkit-inner-spin-button { | |||||
| height: auto; | |||||
| } | |||||
| [type=search] { | |||||
| -webkit-appearance: textfield; | |||||
| outline-offset: -2px; | |||||
| } | |||||
| [type="tel"], | |||||
| [type="url"], | |||||
| [type="email"], | |||||
| [type="number"] { | |||||
| direction: ltr; | |||||
| } | |||||
| ::-webkit-search-decoration { | |||||
| -webkit-appearance: none; | |||||
| } | |||||
| ::-webkit-color-swatch-wrapper { | |||||
| padding: 0; | |||||
| } | |||||
| ::-webkit-file-upload-button { | |||||
| font: inherit; | |||||
| -webkit-appearance: button; | |||||
| } | |||||
| ::file-selector-button { | |||||
| font: inherit; | |||||
| -webkit-appearance: button; | |||||
| } | |||||
| output { | |||||
| display: inline-block; | |||||
| } | |||||
| iframe { | |||||
| border: 0; | |||||
| } | |||||
| summary { | |||||
| display: list-item; | |||||
| cursor: pointer; | |||||
| } | |||||
| progress { | |||||
| vertical-align: baseline; | |||||
| } | |||||
| [hidden] { | |||||
| display: none !important; | |||||
| } | |||||
| /*# sourceMappingURL=bootstrap-reboot.rtl.css.map */ | |||||
| @@ -0,0 +1,23 @@ | |||||
| The MIT License (MIT) | |||||
| Copyright (c) .NET Foundation and Contributors | |||||
| All rights reserved. | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in all | |||||
| copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| SOFTWARE. | |||||
| @@ -0,0 +1,435 @@ | |||||
| /** | |||||
| * @license | |||||
| * Unobtrusive validation support library for jQuery and jQuery Validate | |||||
| * Copyright (c) .NET Foundation. All rights reserved. | |||||
| * Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | |||||
| * @version v4.0.0 | |||||
| */ | |||||
| /*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ | |||||
| /*global document: false, jQuery: false */ | |||||
| (function (factory) { | |||||
| if (typeof define === 'function' && define.amd) { | |||||
| // AMD. Register as an anonymous module. | |||||
| define("jquery.validate.unobtrusive", ['jquery-validation'], factory); | |||||
| } else if (typeof module === 'object' && module.exports) { | |||||
| // CommonJS-like environments that support module.exports | |||||
| module.exports = factory(require('jquery-validation')); | |||||
| } else { | |||||
| // Browser global | |||||
| jQuery.validator.unobtrusive = factory(jQuery); | |||||
| } | |||||
| }(function ($) { | |||||
| var $jQval = $.validator, | |||||
| adapters, | |||||
| data_validation = "unobtrusiveValidation"; | |||||
| function setValidationValues(options, ruleName, value) { | |||||
| options.rules[ruleName] = value; | |||||
| if (options.message) { | |||||
| options.messages[ruleName] = options.message; | |||||
| } | |||||
| } | |||||
| function splitAndTrim(value) { | |||||
| return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g); | |||||
| } | |||||
| function escapeAttributeValue(value) { | |||||
| // As mentioned on http://api.jquery.com/category/selectors/ | |||||
| return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1"); | |||||
| } | |||||
| function getModelPrefix(fieldName) { | |||||
| return fieldName.substr(0, fieldName.lastIndexOf(".") + 1); | |||||
| } | |||||
| function appendModelPrefix(value, prefix) { | |||||
| if (value.indexOf("*.") === 0) { | |||||
| value = value.replace("*.", prefix); | |||||
| } | |||||
| return value; | |||||
| } | |||||
| function onError(error, inputElement) { // 'this' is the form element | |||||
| var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"), | |||||
| replaceAttrValue = container.attr("data-valmsg-replace"), | |||||
| replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null; | |||||
| container.removeClass("field-validation-valid").addClass("field-validation-error"); | |||||
| error.data("unobtrusiveContainer", container); | |||||
| if (replace) { | |||||
| container.empty(); | |||||
| error.removeClass("input-validation-error").appendTo(container); | |||||
| } | |||||
| else { | |||||
| error.hide(); | |||||
| } | |||||
| } | |||||
| function onErrors(event, validator) { // 'this' is the form element | |||||
| var container = $(this).find("[data-valmsg-summary=true]"), | |||||
| list = container.find("ul"); | |||||
| if (list && list.length && validator.errorList.length) { | |||||
| list.empty(); | |||||
| container.addClass("validation-summary-errors").removeClass("validation-summary-valid"); | |||||
| $.each(validator.errorList, function () { | |||||
| $("<li />").html(this.message).appendTo(list); | |||||
| }); | |||||
| } | |||||
| } | |||||
| function onSuccess(error) { // 'this' is the form element | |||||
| var container = error.data("unobtrusiveContainer"); | |||||
| if (container) { | |||||
| var replaceAttrValue = container.attr("data-valmsg-replace"), | |||||
| replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) : null; | |||||
| container.addClass("field-validation-valid").removeClass("field-validation-error"); | |||||
| error.removeData("unobtrusiveContainer"); | |||||
| if (replace) { | |||||
| container.empty(); | |||||
| } | |||||
| } | |||||
| } | |||||
| function onReset(event) { // 'this' is the form element | |||||
| var $form = $(this), | |||||
| key = '__jquery_unobtrusive_validation_form_reset'; | |||||
| if ($form.data(key)) { | |||||
| return; | |||||
| } | |||||
| // Set a flag that indicates we're currently resetting the form. | |||||
| $form.data(key, true); | |||||
| try { | |||||
| $form.data("validator").resetForm(); | |||||
| } finally { | |||||
| $form.removeData(key); | |||||
| } | |||||
| $form.find(".validation-summary-errors") | |||||
| .addClass("validation-summary-valid") | |||||
| .removeClass("validation-summary-errors"); | |||||
| $form.find(".field-validation-error") | |||||
| .addClass("field-validation-valid") | |||||
| .removeClass("field-validation-error") | |||||
| .removeData("unobtrusiveContainer") | |||||
| .find(">*") // If we were using valmsg-replace, get the underlying error | |||||
| .removeData("unobtrusiveContainer"); | |||||
| } | |||||
| function validationInfo(form) { | |||||
| var $form = $(form), | |||||
| result = $form.data(data_validation), | |||||
| onResetProxy = $.proxy(onReset, form), | |||||
| defaultOptions = $jQval.unobtrusive.options || {}, | |||||
| execInContext = function (name, args) { | |||||
| var func = defaultOptions[name]; | |||||
| func && $.isFunction(func) && func.apply(form, args); | |||||
| }; | |||||
| if (!result) { | |||||
| result = { | |||||
| options: { // options structure passed to jQuery Validate's validate() method | |||||
| errorClass: defaultOptions.errorClass || "input-validation-error", | |||||
| errorElement: defaultOptions.errorElement || "span", | |||||
| errorPlacement: function () { | |||||
| onError.apply(form, arguments); | |||||
| execInContext("errorPlacement", arguments); | |||||
| }, | |||||
| invalidHandler: function () { | |||||
| onErrors.apply(form, arguments); | |||||
| execInContext("invalidHandler", arguments); | |||||
| }, | |||||
| messages: {}, | |||||
| rules: {}, | |||||
| success: function () { | |||||
| onSuccess.apply(form, arguments); | |||||
| execInContext("success", arguments); | |||||
| } | |||||
| }, | |||||
| attachValidation: function () { | |||||
| $form | |||||
| .off("reset." + data_validation, onResetProxy) | |||||
| .on("reset." + data_validation, onResetProxy) | |||||
| .validate(this.options); | |||||
| }, | |||||
| validate: function () { // a validation function that is called by unobtrusive Ajax | |||||
| $form.validate(); | |||||
| return $form.valid(); | |||||
| } | |||||
| }; | |||||
| $form.data(data_validation, result); | |||||
| } | |||||
| return result; | |||||
| } | |||||
| $jQval.unobtrusive = { | |||||
| adapters: [], | |||||
| parseElement: function (element, skipAttach) { | |||||
| /// <summary> | |||||
| /// Parses a single HTML element for unobtrusive validation attributes. | |||||
| /// </summary> | |||||
| /// <param name="element" domElement="true">The HTML element to be parsed.</param> | |||||
| /// <param name="skipAttach" type="Boolean">[Optional] true to skip attaching the | |||||
| /// validation to the form. If parsing just this single element, you should specify true. | |||||
| /// If parsing several elements, you should specify false, and manually attach the validation | |||||
| /// to the form when you are finished. The default is false.</param> | |||||
| var $element = $(element), | |||||
| form = $element.parents("form")[0], | |||||
| valInfo, rules, messages; | |||||
| if (!form) { // Cannot do client-side validation without a form | |||||
| return; | |||||
| } | |||||
| valInfo = validationInfo(form); | |||||
| valInfo.options.rules[element.name] = rules = {}; | |||||
| valInfo.options.messages[element.name] = messages = {}; | |||||
| $.each(this.adapters, function () { | |||||
| var prefix = "data-val-" + this.name, | |||||
| message = $element.attr(prefix), | |||||
| paramValues = {}; | |||||
| if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy) | |||||
| prefix += "-"; | |||||
| $.each(this.params, function () { | |||||
| paramValues[this] = $element.attr(prefix + this); | |||||
| }); | |||||
| this.adapt({ | |||||
| element: element, | |||||
| form: form, | |||||
| message: message, | |||||
| params: paramValues, | |||||
| rules: rules, | |||||
| messages: messages | |||||
| }); | |||||
| } | |||||
| }); | |||||
| $.extend(rules, { "__dummy__": true }); | |||||
| if (!skipAttach) { | |||||
| valInfo.attachValidation(); | |||||
| } | |||||
| }, | |||||
| parse: function (selector) { | |||||
| /// <summary> | |||||
| /// Parses all the HTML elements in the specified selector. It looks for input elements decorated | |||||
| /// with the [data-val=true] attribute value and enables validation according to the data-val-* | |||||
| /// attribute values. | |||||
| /// </summary> | |||||
| /// <param name="selector" type="String">Any valid jQuery selector.</param> | |||||
| // $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one | |||||
| // element with data-val=true | |||||
| var $selector = $(selector), | |||||
| $forms = $selector.parents() | |||||
| .addBack() | |||||
| .filter("form") | |||||
| .add($selector.find("form")) | |||||
| .has("[data-val=true]"); | |||||
| $selector.find("[data-val=true]").each(function () { | |||||
| $jQval.unobtrusive.parseElement(this, true); | |||||
| }); | |||||
| $forms.each(function () { | |||||
| var info = validationInfo(this); | |||||
| if (info) { | |||||
| info.attachValidation(); | |||||
| } | |||||
| }); | |||||
| } | |||||
| }; | |||||
| adapters = $jQval.unobtrusive.adapters; | |||||
| adapters.add = function (adapterName, params, fn) { | |||||
| /// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation.</summary> | |||||
| /// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used | |||||
| /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param> | |||||
| /// <param name="params" type="Array" optional="true">[Optional] An array of parameter names (strings) that will | |||||
| /// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and | |||||
| /// mmmm is the parameter name).</param> | |||||
| /// <param name="fn" type="Function">The function to call, which adapts the values from the HTML | |||||
| /// attributes into jQuery Validate rules and/or messages.</param> | |||||
| /// <returns type="jQuery.validator.unobtrusive.adapters" /> | |||||
| if (!fn) { // Called with no params, just a function | |||||
| fn = params; | |||||
| params = []; | |||||
| } | |||||
| this.push({ name: adapterName, params: params, adapt: fn }); | |||||
| return this; | |||||
| }; | |||||
| adapters.addBool = function (adapterName, ruleName) { | |||||
| /// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where | |||||
| /// the jQuery Validate validation rule has no parameter values.</summary> | |||||
| /// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used | |||||
| /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param> | |||||
| /// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value | |||||
| /// of adapterName will be used instead.</param> | |||||
| /// <returns type="jQuery.validator.unobtrusive.adapters" /> | |||||
| return this.add(adapterName, function (options) { | |||||
| setValidationValues(options, ruleName || adapterName, true); | |||||
| }); | |||||
| }; | |||||
| adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) { | |||||
| /// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where | |||||
| /// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and | |||||
| /// one for min-and-max). The HTML parameters are expected to be named -min and -max.</summary> | |||||
| /// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used | |||||
| /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param> | |||||
| /// <param name="minRuleName" type="String">The name of the jQuery Validate rule to be used when you only | |||||
| /// have a minimum value.</param> | |||||
| /// <param name="maxRuleName" type="String">The name of the jQuery Validate rule to be used when you only | |||||
| /// have a maximum value.</param> | |||||
| /// <param name="minMaxRuleName" type="String">The name of the jQuery Validate rule to be used when you | |||||
| /// have both a minimum and maximum value.</param> | |||||
| /// <param name="minAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that | |||||
| /// contains the minimum value. The default is "min".</param> | |||||
| /// <param name="maxAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that | |||||
| /// contains the maximum value. The default is "max".</param> | |||||
| /// <returns type="jQuery.validator.unobtrusive.adapters" /> | |||||
| return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) { | |||||
| var min = options.params.min, | |||||
| max = options.params.max; | |||||
| if (min && max) { | |||||
| setValidationValues(options, minMaxRuleName, [min, max]); | |||||
| } | |||||
| else if (min) { | |||||
| setValidationValues(options, minRuleName, min); | |||||
| } | |||||
| else if (max) { | |||||
| setValidationValues(options, maxRuleName, max); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| adapters.addSingleVal = function (adapterName, attribute, ruleName) { | |||||
| /// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where | |||||
| /// the jQuery Validate validation rule has a single value.</summary> | |||||
| /// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used | |||||
| /// in the data-val-nnnn HTML attribute(where nnnn is the adapter name).</param> | |||||
| /// <param name="attribute" type="String">[Optional] The name of the HTML attribute that contains the value. | |||||
| /// The default is "val".</param> | |||||
| /// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value | |||||
| /// of adapterName will be used instead.</param> | |||||
| /// <returns type="jQuery.validator.unobtrusive.adapters" /> | |||||
| return this.add(adapterName, [attribute || "val"], function (options) { | |||||
| setValidationValues(options, ruleName || adapterName, options.params[attribute]); | |||||
| }); | |||||
| }; | |||||
| $jQval.addMethod("__dummy__", function (value, element, params) { | |||||
| return true; | |||||
| }); | |||||
| $jQval.addMethod("regex", function (value, element, params) { | |||||
| var match; | |||||
| if (this.optional(element)) { | |||||
| return true; | |||||
| } | |||||
| match = new RegExp(params).exec(value); | |||||
| return (match && (match.index === 0) && (match[0].length === value.length)); | |||||
| }); | |||||
| $jQval.addMethod("nonalphamin", function (value, element, nonalphamin) { | |||||
| var match; | |||||
| if (nonalphamin) { | |||||
| match = value.match(/\W/g); | |||||
| match = match && match.length >= nonalphamin; | |||||
| } | |||||
| return match; | |||||
| }); | |||||
| if ($jQval.methods.extension) { | |||||
| adapters.addSingleVal("accept", "mimtype"); | |||||
| adapters.addSingleVal("extension", "extension"); | |||||
| } else { | |||||
| // for backward compatibility, when the 'extension' validation method does not exist, such as with versions | |||||
| // of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for | |||||
| // validating the extension, and ignore mime-type validations as they are not supported. | |||||
| adapters.addSingleVal("extension", "extension", "accept"); | |||||
| } | |||||
| adapters.addSingleVal("regex", "pattern"); | |||||
| adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"); | |||||
| adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range"); | |||||
| adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength"); | |||||
| adapters.add("equalto", ["other"], function (options) { | |||||
| var prefix = getModelPrefix(options.element.name), | |||||
| other = options.params.other, | |||||
| fullOtherName = appendModelPrefix(other, prefix), | |||||
| element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0]; | |||||
| setValidationValues(options, "equalTo", element); | |||||
| }); | |||||
| adapters.add("required", function (options) { | |||||
| // jQuery Validate equates "required" with "mandatory" for checkbox elements | |||||
| if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") { | |||||
| setValidationValues(options, "required", true); | |||||
| } | |||||
| }); | |||||
| adapters.add("remote", ["url", "type", "additionalfields"], function (options) { | |||||
| var value = { | |||||
| url: options.params.url, | |||||
| type: options.params.type || "GET", | |||||
| data: {} | |||||
| }, | |||||
| prefix = getModelPrefix(options.element.name); | |||||
| $.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) { | |||||
| var paramName = appendModelPrefix(fieldName, prefix); | |||||
| value.data[paramName] = function () { | |||||
| var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']"); | |||||
| // For checkboxes and radio buttons, only pick up values from checked fields. | |||||
| if (field.is(":checkbox")) { | |||||
| return field.filter(":checked").val() || field.filter(":hidden").val() || ''; | |||||
| } | |||||
| else if (field.is(":radio")) { | |||||
| return field.filter(":checked").val() || ''; | |||||
| } | |||||
| return field.val(); | |||||
| }; | |||||
| }); | |||||
| setValidationValues(options, "remote", value); | |||||
| }); | |||||
| adapters.add("password", ["min", "nonalphamin", "regex"], function (options) { | |||||
| if (options.params.min) { | |||||
| setValidationValues(options, "minlength", options.params.min); | |||||
| } | |||||
| if (options.params.nonalphamin) { | |||||
| setValidationValues(options, "nonalphamin", options.params.nonalphamin); | |||||
| } | |||||
| if (options.params.regex) { | |||||
| setValidationValues(options, "regex", options.params.regex); | |||||
| } | |||||
| }); | |||||
| adapters.add("fileextensions", ["extensions"], function (options) { | |||||
| setValidationValues(options, "extension", options.params.extensions); | |||||
| }); | |||||
| $(function () { | |||||
| $jQval.unobtrusive.parse(document); | |||||
| }); | |||||
| return $jQval.unobtrusive; | |||||
| })); | |||||
| @@ -0,0 +1,22 @@ | |||||
| The MIT License (MIT) | |||||
| ===================== | |||||
| Copyright Jörn Zaefferer | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in | |||||
| all copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
| THE SOFTWARE. | |||||
| @@ -0,0 +1,21 @@ | |||||
| Copyright OpenJS Foundation and other contributors, https://openjsf.org/ | |||||
| Permission is hereby granted, free of charge, to any person obtaining | |||||
| a copy of this software and associated documentation files (the | |||||
| "Software"), to deal in the Software without restriction, including | |||||
| without limitation the rights to use, copy, modify, merge, publish, | |||||
| distribute, sublicense, and/or sell copies of the Software, and to | |||||
| permit persons to whom the Software is furnished to do so, subject to | |||||
| the following conditions: | |||||
| The above copyright notice and this permission notice shall be | |||||
| included in all copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||||