Carry the level everywhere a position is drawn, and gate it per occurrence

This commit is contained in:
2026-08-18 09:37:29 -04:00
parent 8dd383f4ee
commit 09e9d5f047
16 changed files with 516 additions and 400 deletions

View File

@@ -43,14 +43,13 @@
:class="[`pos-${pos}`, page[pos - 1] ? 'filled' : 'empty']"
>
<template v-if="page[pos - 1]">
<div class="model-name">{{ page[pos - 1].printer?.modelname || '' }}</div>
<div class="csf-name">{{ labelName(page[pos - 1]) }}</div>
<div class="qr-container">
<img v-if="qrImages[`${pageIdx}-${pos}`]" :src="qrImages[`${pageIdx}-${pos}`]" class="qr-img" alt="QR" />
</div>
<div class="info-section">
<div class="csf-name">{{ page[pos - 1].assetnumber }}</div>
<div class="info-inner">
<div v-if="page[pos - 1].printer?.windowsname" class="info-row">{{ page[pos - 1].printer.windowsname }}</div>
<div v-if="fqdnFor(page[pos - 1])" class="info-row">{{ fqdnFor(page[pos - 1]) }}</div>
<div v-if="getIp(page[pos - 1])" class="info-row">{{ getIp(page[pos - 1]) }}</div>
</div>
</div>
@@ -67,6 +66,7 @@ import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { printersApi } from '@/api'
import { renderQrDataUrl } from '@/utils/codes'
import { buildQrUrl } from '@/utils/qrTarget'
import { getPrinterHostnameTemplate } from '@/utils/siteSettings'
const printers = ref([])
const selectedPrinters = ref([])
@@ -91,6 +91,7 @@ const pages = computed(() => {
onMounted(async () => {
try {
hostnameTemplate.value = await getPrinterHostnameTemplate()
// listAll: perpage is clamped to 100, and a batch sheet must cover every
// printer, not the first page of them.
printers.value = await printersApi.listAll()
@@ -129,6 +130,29 @@ async function generateQRCodes() {
qrImages.value = next
}
// The printer's FQDN. A stored hostname wins; otherwise the site builds one
// from the IP through the printer_hostname_template setting (ADR-015), which is
// how PrinterForm and the toner report derive it.
const hostnameTemplate = ref('')
function fqdnFor(item) {
const stored = item?.printer?.hostname
if (stored) return stored
const ip = getIp(item)
if (!ip || !hostnameTemplate.value) return ''
return hostnameTemplate.value.replace('{ip}', ip.replace(/\./g, '-'))
}
// What goes on the label's prominent top line: the printer's NAME, e.g.
// 8201-HPLaserJetPro. Sites keep that name in different places - the Windows
// queue name when one is set, otherwise the asset's name, and in practice most
// printers carry it as the assetnumber and nothing else, so that is the last
// fallback rather than a blank label. This is a name, not an identifier line:
// the label deliberately carries no separate "Asset #".
function labelName(item) {
return item?.printer?.windowsname || item?.name || item?.assetnumber || ''
}
function displayName(printer) {
return printer.assetnumber || printer.name || `Printer-${printer.assetid}`
}
@@ -286,7 +310,7 @@ function print() {
.info-section { margin-top: 0.1in; display: flex; flex-direction: column; align-items: center; }
.info-inner { text-align: left; }
.info-row { font-size: 9pt; color: #333; margin: 1px 0; white-space: nowrap; }
.csf-name { font-size: 12pt; font-weight: bold; font-family: monospace; text-align: center; margin-bottom: 2px; color: #000; }
.csf-name { font-size: 12pt; font-weight: bold; font-family: monospace; text-align: center; margin-bottom: 0.06in; color: #000; }
.empty-label { color: #999; font-size: 14px; }
@media print {