Verbessere Image Generator: Fixierter Crop-Rahmen, optimierte Vorschau-Skalierung
- Crop-Rahmen ist fixiert (nicht verschiebbar/skalierbar), nur Bild bewegbar - Zuschnitt auf exakt 369×492 Pixel - Finales Badge im A4-Format (794×1123px / 21×29.7cm) - Vorschau auf 35% skaliert mit korrektem Platzverbrauch - Große Bilder passen initial in Container, unbegrenztes Zoomen möglich - Badge-Template behält alle mm/cm-Maße aus print.html
This commit is contained in:
parent
67184e7e01
commit
d6c2986ebf
|
|
@ -70,6 +70,7 @@
|
|||
margin: 0 auto;
|
||||
padding: 5mm 0 0 0;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.badge-template .your-name {
|
||||
|
|
@ -87,8 +88,9 @@
|
|||
}
|
||||
|
||||
.badge-template .warnung {
|
||||
font-size: 10mm;
|
||||
font-size: 9mm;
|
||||
color: red;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,11 +41,14 @@ class EmployeeImageGenerator extends Component {
|
|||
this.badgeTemplate = useRef("badgeTemplate");
|
||||
|
||||
this.cropper = null;
|
||||
// A4-Format wie Original: 794×1123px (wie thekenheld)
|
||||
this.targetWidth = 794;
|
||||
this.targetHeight = 1123;
|
||||
// Umrechnungsfaktor: 793px / 210mm = 3.776 px/mm
|
||||
this.pxPerMm = this.targetWidth / 210;
|
||||
// Zielgröße für das zugeschnittene Foto
|
||||
this.cropWidth = 369;
|
||||
this.cropHeight = 492;
|
||||
// Zielgröße für das finale Badge (A4-Format)
|
||||
this.badgeWidth = 794;
|
||||
this.badgeHeight = 1123;
|
||||
// Umrechnungsfaktor für mm (falls benötigt)
|
||||
this.pxPerMm = this.badgeWidth / 210;
|
||||
|
||||
// Lade Company-Logo beim Start
|
||||
this.loadCompanyLogo();
|
||||
|
|
@ -107,20 +110,29 @@ class EmployeeImageGenerator extends Component {
|
|||
return;
|
||||
}
|
||||
|
||||
const aspectRatio = this.targetWidth / this.targetHeight;
|
||||
const aspectRatio = this.cropWidth / this.cropHeight;
|
||||
|
||||
this.cropper = new window.Cropper(imageElement, {
|
||||
aspectRatio: aspectRatio,
|
||||
viewMode: 3,
|
||||
dragMode: 'move',
|
||||
autoCropArea: 1,
|
||||
viewMode: 1, // Erlaubt unbegrenztes Zoomen
|
||||
dragMode: 'move', // Bild verschieben
|
||||
autoCropArea: 0.7, // Crop-Rahmen ist 70% des Containers - besser sichtbar
|
||||
restore: false,
|
||||
guides: true,
|
||||
center: true,
|
||||
highlight: false,
|
||||
cropBoxMovable: true,
|
||||
cropBoxResizable: true,
|
||||
cropBoxMovable: false, // Crop-Rahmen NICHT verschiebbar
|
||||
cropBoxResizable: false, // Crop-Rahmen NICHT in Größe veränderbar
|
||||
toggleDragModeOnDblclick: false,
|
||||
zoomable: true,
|
||||
zoomOnWheel: true, // Zoom mit Mausrad
|
||||
zoomOnTouch: true, // Zoom mit Touch-Gesten
|
||||
minContainerWidth: 200,
|
||||
minContainerHeight: 200,
|
||||
ready: function() {
|
||||
// Setze initiales Zoom-Level auf "fit in container"
|
||||
this.cropper.zoomTo(0);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -131,8 +143,8 @@ class EmployeeImageGenerator extends Component {
|
|||
}
|
||||
|
||||
const canvas = this.cropper.getCroppedCanvas({
|
||||
width: this.targetWidth,
|
||||
height: this.targetHeight,
|
||||
width: this.cropWidth,
|
||||
height: this.cropHeight,
|
||||
});
|
||||
|
||||
if (canvas) {
|
||||
|
|
@ -163,16 +175,30 @@ class EmployeeImageGenerator extends Component {
|
|||
return;
|
||||
}
|
||||
|
||||
// Konvertiere HTML-Element zu Canvas mit html2canvas (exakt wie thekenheld)
|
||||
// Entferne temporär die Skalierung vom Wrapper (nicht vom Badge selbst)
|
||||
const wrapper = badgeElement.parentElement;
|
||||
const originalTransform = wrapper ? wrapper.style.transform : '';
|
||||
if (wrapper) {
|
||||
wrapper.style.transform = 'none';
|
||||
}
|
||||
|
||||
// Konvertiere HTML-Element zu Canvas mit html2canvas in voller A4-Größe
|
||||
const canvas = await window.html2canvas(badgeElement, {
|
||||
width: this.targetWidth,
|
||||
height: this.targetHeight,
|
||||
width: this.badgeWidth,
|
||||
height: this.badgeHeight,
|
||||
scale: 1, // Wichtig: keine zusätzliche Skalierung
|
||||
backgroundColor: '#ffffff',
|
||||
});
|
||||
|
||||
// Konvertiere Canvas zu Base64 PNG (wie Original thekenheld)
|
||||
// Stelle die Skalierung für die Vorschau wieder her
|
||||
if (wrapper) {
|
||||
wrapper.style.transform = originalTransform;
|
||||
}
|
||||
|
||||
// Konvertiere Canvas zu Base64 PNG
|
||||
const finalImageBase64 = canvas.toDataURL('image/png');
|
||||
|
||||
// Speichere in Odoo (entferne data:image/jpeg;base64, prefix)
|
||||
// Speichere in Odoo (entferne data:image/png;base64, prefix)
|
||||
const imageData = finalImageBase64.split(',')[1];
|
||||
|
||||
await this.orm.write('hr.employee', [this.state.employeeId], {
|
||||
|
|
|
|||
|
|
@ -54,10 +54,10 @@
|
|||
<i class="fa fa-info-circle me-2"/>
|
||||
Bewege und zoome das Bild, um den gewünschten Ausschnitt zu wählen
|
||||
</div>
|
||||
<div class="img-container" style="max-height: 500px; overflow: hidden;">
|
||||
<div class="img-container" style="max-height: 500px; overflow: hidden; display: flex; align-items: center; justify-content: center;">
|
||||
<img t-ref="cropperImage"
|
||||
t-att-src="state.imageDataUrl"
|
||||
style="max-width: 100%; display: block;"/>
|
||||
style="max-width: 100%; max-height: 100%; display: block;"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -103,32 +103,37 @@
|
|||
<!-- Rechte Spalte: Vorschau -->
|
||||
<div class="col-md-6">
|
||||
<h5 class="mb-3">Vorschau</h5>
|
||||
<div class="border rounded p-2 bg-light" style="overflow-y: auto; max-height: 600px;">
|
||||
<!-- HTML Badge Template (wird mit html2canvas konvertiert) -->
|
||||
<div t-ref="badgeTemplate" class="badge-template">
|
||||
<header>
|
||||
<div class="logo">
|
||||
<img t-if="state.companyLogo" t-att-src="state.companyLogo" alt="Logo" class="logo"/>
|
||||
<div class="border rounded p-3 d-flex justify-content-center align-items-center"
|
||||
style="background: rgb(204, 204, 204); min-height: 400px;">
|
||||
<!-- Wrapper nimmt nur den skalierten Platz ein (21cm × 29.7cm × 0.35 = 7.35cm × 10.4cm) -->
|
||||
<div style="width: 7.35cm; height: 10.4cm; position: relative;">
|
||||
<div style="width: 21cm; height: 29.7cm; transform: scale(0.35); transform-origin: top left; position: absolute; top: 0; left: 0;">
|
||||
<div t-ref="badgeTemplate" class="badge-template">
|
||||
<header>
|
||||
<div class="logo">
|
||||
<img t-if="state.companyLogo" t-att-src="state.companyLogo" alt="Logo" class="logo"/>
|
||||
</div>
|
||||
</header>
|
||||
<div class="content">
|
||||
<div class="headline1">An der Theke für Dich</div>
|
||||
<div class="headline2">Werkstattaufsicht und Werkzeugausgabe</div>
|
||||
<img t-if="state.croppedImageDataUrl"
|
||||
t-att-src="state.croppedImageDataUrl"
|
||||
alt="avatar"
|
||||
class="centered-image"/>
|
||||
<div class="your-name" t-esc="state.employeeName || 'Dein Name'"/>
|
||||
<hr/>
|
||||
<div class="headline3">Themenschwerpunkte:</div>
|
||||
<div class="topics" t-esc="state.jobFocus || 'Deine Schwerpunkte'"/>
|
||||
|
||||
</div>
|
||||
<footer><div class="warnung">! Zugang zur Theke nur für Berechtigte !</div></footer>
|
||||
</div>
|
||||
</header>
|
||||
<div class="content">
|
||||
<div class="headline1">An der Theke für Dich</div>
|
||||
<div class="headline2">Werkstattaufsicht und Werkzeugausgabe</div>
|
||||
<img t-if="state.croppedImageDataUrl"
|
||||
t-att-src="state.croppedImageDataUrl"
|
||||
alt="avatar"
|
||||
class="centered-image"/>
|
||||
<div class="your-name" t-esc="state.employeeName || 'Dein Name'"/>
|
||||
<hr/>
|
||||
<div class="headline3">Themenschwerpunkte:</div>
|
||||
<div class="topics" t-esc="state.jobFocus || 'Deine Schwerpunkte'"/>
|
||||
<div class="warnung">! Zugang zur Theke nur für Berechtigte !</div>
|
||||
</div>
|
||||
<footer></footer>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted d-block mt-2 text-center">
|
||||
A4-Format (793 × 1122 Pixel) - 50% Vorschau
|
||||
A4-Format (21×29.7cm) - 35% Vorschau
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user