mirror of
https://github.com/mivodev/mivo.git
synced 2026-01-26 05:25:42 +07:00
Initial Release v1.0.0: Full feature set with Docker automation, Nginx/Alpine stack
This commit is contained in:
144
app/Views/print/custom.php
Normal file
144
app/Views/print/custom.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Print Voucher</title>
|
||||
<style>
|
||||
@media print {
|
||||
.no-print { display: none !important; }
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-print-color-adjust: exact !important;
|
||||
print-color-adjust: exact !important;
|
||||
}
|
||||
* {
|
||||
-webkit-print-color-adjust: exact !important;
|
||||
print-color-adjust: exact !important;
|
||||
}
|
||||
}
|
||||
body { margin: 0; padding: 0; background: #eee; font-family: sans-serif; }
|
||||
.voucher-wrapper {
|
||||
/* Wrapper for page break control if needed */
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
</style>
|
||||
<script src="/assets/js/qrious.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<?php include __DIR__ . '/toolbar.php'; ?>
|
||||
|
||||
<div style="padding: 20px; text-align: center;">
|
||||
<?php foreach($users as $index => $u): ?>
|
||||
<div class="voucher-wrapper">
|
||||
<?php
|
||||
$html = $templateContent;
|
||||
// Replace Variables
|
||||
// Standard variables
|
||||
$replacements = [
|
||||
'{{username}}' => $u['username'],
|
||||
'{{password}}' => $u['password'],
|
||||
'{{price}}' => $u['price'],
|
||||
'{{validity}}' => $u['validity'],
|
||||
'{{timelimit}}' => $u['timelimit'] ?? $u['validity'], // Fallback if missing
|
||||
'{{datalimit}}' => $u['datalimit'] ?? '',
|
||||
'{{profile}}' => $u['profile'],
|
||||
'{{comment}}' => $u['comment'],
|
||||
'{{hotspotname}}' => $u['hotspotname'],
|
||||
'{{dns_name}}' => $u['dns_name'],
|
||||
'{{login_url}}' => $u['login_url'],
|
||||
'{{num}}' => ($index + 1),
|
||||
'{{logo}}' => '<img src="/assets/img/logo.png" style="height:30px;border:0;">', // Default Logo placeholder
|
||||
];
|
||||
|
||||
// 1. Handle {{logo id=...}}
|
||||
$html = preg_replace_callback('/\{\{logo\s+id=[\'"]?([^\'"\s]+)[\'"]?\}\}/i', function($matches) use ($logoMap) {
|
||||
$id = $matches[1];
|
||||
if (isset($logoMap[$id])) {
|
||||
return '<img src="' . $logoMap[$id] . '" style="height:50px; width:auto;">'; // Default style, user can wrap in div
|
||||
}
|
||||
return ''; // Return empty if not found
|
||||
}, $html);
|
||||
|
||||
foreach ($replacements as $key => $val) {
|
||||
$html = str_replace($key, $val, $html);
|
||||
}
|
||||
|
||||
// 2. Handle QR Code with Logo support
|
||||
$html = preg_replace_callback('/\{\{qrcode(?:\s+(.*?))?\}\}/i', function($matches) use ($index, $u, $logoMap) {
|
||||
$qrId = "qr-custom-" . $index . "-" . uniqid();
|
||||
$qrCodeValue = $u['login_url'] . "?user=" . $u['username'] . "&password=" . $u['password'];
|
||||
|
||||
// Default Options
|
||||
$opts = [
|
||||
'element' => 'document.getElementById("'.$qrId.'")',
|
||||
'value' => $qrCodeValue,
|
||||
'size' => 100,
|
||||
'foreground' => 'black',
|
||||
'background' => 'white',
|
||||
'padding' => null,
|
||||
'logo' => null // Logo ID
|
||||
];
|
||||
|
||||
$rounded = '';
|
||||
|
||||
// Parse Attributes
|
||||
if (!empty($matches[1])) {
|
||||
$attrs = $matches[1];
|
||||
if (preg_match('/fg\s*=\s*[\'"]?([^\'"\s]+)[\'"]?/i', $attrs, $m)) $opts['foreground'] = $m[1];
|
||||
if (preg_match('/bg\s*=\s*[\'"]?([^\'"\s]+)[\'"]?/i', $attrs, $m)) $opts['background'] = $m[1];
|
||||
if (preg_match('/size\s*=\s*[\'"]?(\d+)[\'"]?/i', $attrs, $m)) $opts['size'] = $m[1];
|
||||
if (preg_match('/padding\s*=\s*[\'"]?(\d+)[\'"]?/i', $attrs, $m)) $opts['padding'] = $m[1];
|
||||
if (preg_match('/rounded\s*=\s*[\'"]?(\d+)[\'"]?/i', $attrs, $m)) $rounded = 'border-radius: ' . $m[1] . 'px;';
|
||||
if (preg_match('/logo\s*=\s*[\'"]?([^\'"\s]+)[\'"]?/i', $attrs, $m)) $opts['logo'] = $m[1];
|
||||
}
|
||||
|
||||
// CSS Styles
|
||||
$cssPadding = $opts['padding'] ? ('padding: ' . $opts['padding'] . 'px; ') : '';
|
||||
$cssBg = 'background-color: ' . $opts['background'] . '; ';
|
||||
$baseStyle = 'display: inline-block; vertical-align: middle; ' . $cssBg . $cssPadding . $rounded;
|
||||
|
||||
// JS Generation
|
||||
$qrJs = "
|
||||
(function() {
|
||||
var qr = new QRious({
|
||||
element: document.getElementById('$qrId'),
|
||||
value: \"{$opts['value']}\",
|
||||
size: {$opts['size']},
|
||||
foreground: \"{$opts['foreground']}\",
|
||||
backgroundAlpha: 0
|
||||
});
|
||||
";
|
||||
|
||||
// If Logo is requested and found
|
||||
if ($opts['logo'] && isset($logoMap[$opts['logo']])) {
|
||||
$logoPath = $logoMap[$opts['logo']];
|
||||
$qrJs .= "
|
||||
var img = new Image();
|
||||
img.src = '$logoPath';
|
||||
img.onload = function() {
|
||||
var canvas = document.getElementById('$qrId');
|
||||
var ctx = canvas.getContext('2d');
|
||||
var size = {$opts['size']};
|
||||
var logoSize = size * 0.2; // Logo is 20% of QR size
|
||||
var logoPos = (size - logoSize) / 2;
|
||||
ctx.drawImage(img, logoPos, logoPos, logoSize, logoSize);
|
||||
};
|
||||
";
|
||||
}
|
||||
|
||||
$qrJs .= "})();";
|
||||
|
||||
return '<canvas id="'.$qrId.'" style="'.$baseStyle.'"></canvas><script>'.$qrJs.'</script>';
|
||||
}, $html);
|
||||
|
||||
echo $html;
|
||||
?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
76
app/Views/print/default.php
Normal file
76
app/Views/print/default.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Print Voucher</title>
|
||||
<style>
|
||||
@media print {
|
||||
.no-print { display: none !important; }
|
||||
body { margin: 0; padding: 0; }
|
||||
}
|
||||
body { font-family: 'Courier New', Courier, monospace; background: #eee; }
|
||||
.voucher-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.voucher {
|
||||
width: 300px;
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
border: 1px solid #ccc;
|
||||
page-break-inside: avoid;
|
||||
display: inline-block;
|
||||
}
|
||||
.header { text-align: center; font-weight: bold; margin-bottom: 5px; }
|
||||
.row { display: flex; justify-content: space-between; margin-bottom: 2px; font-size: 12px; }
|
||||
.code { font-size: 16px; font-weight: bold; text-align: center; margin: 10px 0; border: 1px dashed #000; padding: 5px; }
|
||||
.qr-wrapper { text-align: center; margin-top: 5px; }
|
||||
</style>
|
||||
<script src="/assets/js/qrious.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<?php include __DIR__ . '/toolbar.php'; ?>
|
||||
|
||||
<div class="voucher-container">
|
||||
<?php foreach($users as $index => $u): ?>
|
||||
<div class="voucher">
|
||||
<div class="header"><?= htmlspecialchars($u['dns_name']) ?></div>
|
||||
<div class="row"><span>Profile:</span> <span><?= htmlspecialchars($u['profile']) ?></span></div>
|
||||
<div class="row"><span>Valid:</span> <span><?= htmlspecialchars($u['validity']) ?></span></div>
|
||||
<div class="row"><span>Price:</span> <span><?= htmlspecialchars($u['price']) ?></span></div>
|
||||
|
||||
<div class="code">
|
||||
User: <?= htmlspecialchars($u['username']) ?><br>
|
||||
Pass: <?= htmlspecialchars($u['password']) ?>
|
||||
</div>
|
||||
|
||||
<div class="qr-wrapper">
|
||||
<canvas id="qr-<?= $index ?>"></canvas>
|
||||
</div>
|
||||
|
||||
<div style="text-align:center; font-size: 10px; margin-top:5px;">
|
||||
Login: <?= htmlspecialchars($u['login_url']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
new QRious({
|
||||
element: document.getElementById('qr-<?= $index ?>'),
|
||||
value: '<?= htmlspecialchars($u['login_url']) ?>?user=<?= htmlspecialchars($u['username']) ?>&password=<?= htmlspecialchars($u['password']) ?>',
|
||||
size: 100
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Optional: Auto print if directed
|
||||
// window.print();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
45
app/Views/print/toolbar.php
Normal file
45
app/Views/print/toolbar.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
// Print Toolbar (No Print)
|
||||
// Expected variables: $templates (array), $currentTemplate (id or 'default'), $session (string)
|
||||
// Also preserves current query params (like ids=...)
|
||||
|
||||
$currentQuery = $_GET;
|
||||
unset($currentQuery['template']); // Remove old template param
|
||||
$queryString = http_build_query($currentQuery);
|
||||
$baseUrl = strtok($_SERVER["REQUEST_URI"], '?');
|
||||
|
||||
// If ids is missing (e.g. Quick Print single ID in segment), we don't need to append it if it's not in GET.
|
||||
// Logic: Reload current URL but with new template param.
|
||||
?>
|
||||
<div class="no-print" style="position: sticky; top:0; background: #fff; border-bottom: 1px solid #ddd; padding: 10px; z-index: 50; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<label style="font-size: 14px; font-weight: bold; color: #333;">Template:</label>
|
||||
<select onchange="changeTemplate(this.value)" style="padding: 5px; border: 1px solid #ccc; rounded: 4px;">
|
||||
<option value="default" <?= $currentTemplate === 'default' ? 'selected' : '' ?>>Default Thermal</option>
|
||||
<?php if (!empty($templates)): ?>
|
||||
<?php foreach ($templates as $t): ?>
|
||||
<option value="<?= $t['id'] ?>" <?= (string)$currentTemplate === (string)$t['id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($t['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button onclick="window.print()" style="padding: 6px 16px; background: #0070f3; color: white; border: none; border-radius: 4px; font-weight: bold; cursor: pointer;">Print</button>
|
||||
<button onclick="window.close()" style="padding: 6px 16px; background: #eee; border: 1px solid #ccc; border-radius: 4px; cursor: pointer; margin-left: 5px;">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function changeTemplate(val) {
|
||||
const currentUrl = new URL(window.location.href);
|
||||
if (val === 'default') {
|
||||
currentUrl.searchParams.delete('template');
|
||||
} else {
|
||||
currentUrl.searchParams.set('template', val);
|
||||
}
|
||||
window.location.href = currentUrl.toString();
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user