| @@ -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. | |||