Erstes Formular + QR Integration
This commit is contained in:
parent
b0edcb2af1
commit
697367dd54
@ -4,14 +4,13 @@
|
||||
<CascadingAuthenticationState>
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<PageTitle>Not found</PageTitle>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
<p>Seite nicht gefunden.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
</CascadingAuthenticationState>
|
||||
</CascadingAuthenticationState>
|
||||
|
||||
9
FilterCair.Client/Pages/Authentication.razor
Normal file
9
FilterCair.Client/Pages/Authentication.razor
Normal file
@ -0,0 +1,9 @@
|
||||
@page "/authentication/{action}"
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
|
||||
|
||||
<RemoteAuthenticatorView Action="@Action" />
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string? Action { get; set; }
|
||||
}
|
||||
91
FilterCair.Client/Pages/FilterForm.razor
Normal file
91
FilterCair.Client/Pages/FilterForm.razor
Normal file
@ -0,0 +1,91 @@
|
||||
@page "/filterform"
|
||||
@inject IJSRuntime JS
|
||||
|
||||
@using FilterCair.Shared.Models
|
||||
|
||||
<PageTitle>Filterdaten erfassen</PageTitle>
|
||||
|
||||
<div class="container py-4">
|
||||
<h4 class="mb-4 text-center">Filterdaten erfassen</h4>
|
||||
|
||||
<EditForm Model="@filter" OnValidSubmit="SaveForm">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Filter-ID</label>
|
||||
<InputText class="form-control" @bind-Value="filter.FilterId" placeholder="z. B. FC-A-234" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Halle</label>
|
||||
<InputSelect class="form-select" @bind-Value="filter.Halle">
|
||||
<option value="">– bitte wählen –</option>
|
||||
@foreach (var h in halls)
|
||||
{
|
||||
<option value="@h">@h</option>
|
||||
}
|
||||
</InputSelect>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Zustand</label>
|
||||
<InputSelect class="form-select" @bind-Value="filter.Zustand">
|
||||
<option value="">– bitte wählen –</option>
|
||||
<option>In Ordnung</option>
|
||||
<option>Verschmutzt</option>
|
||||
<option>Defekt</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Luftdruck (Pa)</label>
|
||||
<InputNumber class="form-control" @bind-Value="filter.Luftdruck" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Bemerkung</label>
|
||||
<InputTextArea class="form-control" rows="3" @bind-Value="filter.Bemerkung" />
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success w-100">
|
||||
Speichern
|
||||
</button>
|
||||
</EditForm>
|
||||
|
||||
@if (saved)
|
||||
{
|
||||
<div class="alert alert-success mt-4 text-center">
|
||||
Daten gespeichert!
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private FilterModel filter = new();
|
||||
private bool saved = false;
|
||||
private List<string> halls = new() { "Halle A", "Halle B", "Halle C" };
|
||||
|
||||
private async Task SaveForm()
|
||||
{
|
||||
saved = true;
|
||||
Console.WriteLine($"Gespeichert: {System.Text.Json.JsonSerializer.Serialize(filter)}");
|
||||
|
||||
// später → JS.InvokeVoidAsync("IndexedDB.saveFilter", filter);
|
||||
await JS.InvokeVoidAsync("console.log", "Filter gespeichert:", filter);
|
||||
}
|
||||
|
||||
// optional: QR-Ergebnis vorbelegen
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var qrParam = NavManager?.ToAbsoluteUri(NavManager.Uri).Query;
|
||||
if (!string.IsNullOrEmpty(qrParam))
|
||||
{
|
||||
// z. B. ?id=FC-123
|
||||
var parts = System.Web.HttpUtility.ParseQueryString(qrParam);
|
||||
filter.FilterId = parts.Get("id");
|
||||
}
|
||||
}
|
||||
|
||||
[Inject] NavigationManager? NavManager { get; set; }
|
||||
}
|
||||
@ -110,7 +110,8 @@
|
||||
|
||||
private void Login()
|
||||
{
|
||||
Nav.NavigateTo("authentication/login");
|
||||
Nav.NavigateTo("authentication/login", forceLoad: true);
|
||||
|
||||
}
|
||||
|
||||
private void Logout()
|
||||
|
||||
53
FilterCair.Client/Pages/QRScanner.razor
Normal file
53
FilterCair.Client/Pages/QRScanner.razor
Normal file
@ -0,0 +1,53 @@
|
||||
@page "/qrscanner"
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<PageTitle>QR-Scanner</PageTitle>
|
||||
|
||||
<div class="container py-4 text-center">
|
||||
<h4 class="mb-3">📷 QR-Code Scanner</h4>
|
||||
|
||||
<div class="mb-3">
|
||||
<video id="video" autoplay playsinline class="border rounded shadow-sm" style="width:100%;max-width:400px;"></video>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(result))
|
||||
{
|
||||
<div class="alert alert-success mt-3">
|
||||
<strong>Erkannt:</strong> @result
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-secondary me-2" @onclick="StartScan">Neu starten</button>
|
||||
<button class="btn btn-outline-danger" @onclick="StopScan">Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string? result;
|
||||
|
||||
private async Task StartScan()
|
||||
{
|
||||
await JS.InvokeVoidAsync("QRScanner.start");
|
||||
}
|
||||
|
||||
private async Task StopScan()
|
||||
{
|
||||
await JS.InvokeVoidAsync("QRScanner.stop");
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public void OnQrDetected(string code)
|
||||
{
|
||||
result = code;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await JS.InvokeVoidAsync("QRScanner.init", DotNetObjectReference.Create(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,13 +7,12 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
|
||||
var apiBase = builder.Configuration["Api:BaseUrl"];
|
||||
|
||||
builder.Services.AddHttpClient("FilterCair.ServerAPI", client =>
|
||||
client.BaseAddress = new Uri(apiBase))
|
||||
client.BaseAddress = new Uri(builder.Configuration["Api:BaseUrl"] ?? "https://localhost:7010"))
|
||||
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
|
||||
|
||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(apiBase) });
|
||||
builder.Services.AddScoped(sp =>
|
||||
sp.GetRequiredService<IHttpClientFactory>().CreateClient("FilterCair.ServerAPI"));
|
||||
|
||||
builder.Services.AddMsalAuthentication(options =>
|
||||
{
|
||||
@ -22,4 +21,4 @@ builder.Services.AddMsalAuthentication(options =>
|
||||
"api://54b010c7-4a9a-4f2d-bea3-9faba3f12495/API.Access");
|
||||
});
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
await builder.Build().RunAsync();
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
{
|
||||
"ApiUrl": "https://filtercair-api.azurewebsites.net",
|
||||
"AzureAd": {
|
||||
"Authority": "https://login.microsoftonline.com/66c8d79d-dbbb-46ca-9b6d-3b942f463abe",
|
||||
"ClientId": "54b010c7-4a9a-4f2d-bea3-9faba3f12495",
|
||||
"ValidateAuthority": true
|
||||
"ValidateAuthority": true,
|
||||
"RedirectUri": "https://filtercair-client-efava4bfgvamhkfu.westeurope-01.azurewebsites.net/authentication/login-callback",
|
||||
"PostLogoutRedirectUri": "https://filtercair-client-efava4bfgvamhkfu.westeurope-01.azurewebsites.net/",
|
||||
"CacheLocation": "localStorage",
|
||||
"Scopes": [
|
||||
"openid",
|
||||
"profile",
|
||||
"email",
|
||||
"api://54b010c7-4a9a-4f2d-bea3-9faba3f12495/API.Access"
|
||||
]
|
||||
},
|
||||
"Api": {
|
||||
"Scopes": [ "api://54b010c7-4a9a-4f2d-bea3-9faba3f12495/API.Access" ],
|
||||
"BaseUrl": "https://localhost:7010"
|
||||
"BaseUrl": "https://filtercair-api.azurewebsites.net"
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.js"></script>
|
||||
<script src="js/filtercair.js"></script>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js"></script>
|
||||
|
||||
@ -19,4 +19,70 @@
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
window.QRScanner = {
|
||||
video: null,
|
||||
stream: null,
|
||||
dotnetRef: null,
|
||||
scanning: false,
|
||||
|
||||
init: function (dotnetRef) {
|
||||
this.dotnetRef = dotnetRef;
|
||||
console.log("QRScanner initialized");
|
||||
},
|
||||
|
||||
start: async function () {
|
||||
if (!this.video) {
|
||||
this.video = document.getElementById("video");
|
||||
}
|
||||
try {
|
||||
this.stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } });
|
||||
this.video.srcObject = this.stream;
|
||||
this.scanning = true;
|
||||
this.scanLoop();
|
||||
} catch (err) {
|
||||
alert("Kamera konnte nicht gestartet werden: " + err);
|
||||
}
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
if (this.stream) {
|
||||
this.stream.getTracks().forEach(t => t.stop());
|
||||
}
|
||||
this.scanning = false;
|
||||
},
|
||||
|
||||
scanLoop: async function () {
|
||||
if (!this.video) return;
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
const jsQRAvailable = typeof jsQR !== 'undefined';
|
||||
|
||||
if (!jsQRAvailable) {
|
||||
console.warn("jsQR not loaded yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
const tick = () => {
|
||||
if (!this.scanning) return;
|
||||
if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) {
|
||||
canvas.width = this.video.videoWidth;
|
||||
canvas.height = this.video.videoHeight;
|
||||
context.drawImage(this.video, 0, 0, canvas.width, canvas.height);
|
||||
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const code = jsQR(imageData.data, imageData.width, imageData.height);
|
||||
if (code) {
|
||||
console.log("QR erkannt:", code.data);
|
||||
this.dotnetRef.invokeMethodAsync("OnQrDetected", code.data);
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
};
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
namespace FilterCair.Shared;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
22
FilterCair.Shared/Models/FilterModel.cs
Normal file
22
FilterCair.Shared/Models/FilterModel.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FilterCair.Shared.Models
|
||||
{
|
||||
public class FilterModel
|
||||
{
|
||||
[Required]
|
||||
public string? FilterId { get; set; }
|
||||
|
||||
[Required]
|
||||
public string? Halle { get; set; }
|
||||
|
||||
public string? Zustand { get; set; }
|
||||
public double? Luftdruck { get; set; }
|
||||
public string? Bemerkung { get; set; }
|
||||
}
|
||||
}
|
||||
@ -59,4 +59,7 @@ Global
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {57097AF9-FD61-420B-B0E9-520CF180D7FC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Loading…
Reference in New Issue
Block a user