feat(client): 价签默认模板品名默认展示 + 二维码放大留小边框

- 默认模板改为代码只读基准:readLabelTemplates 加载时强制用
  builtinDefault() 覆盖 builtin-default 项,忽略存储里的旧脏几何
  (旧默认品名被关/旧 QR 坐标等一律自愈回代码基准)
- 二维码放大 x198/y44/尺寸96 → x192/y32/尺寸120,四周留 ~1mm 小边框
- 原型 label-template-editor.html 高级 QR 输入同步;golden 重生
- 新增存储测试钉死「内置默认永远回代码基准」

原型与代码同提交(design-first 双边一致)
This commit is contained in:
wangjia
2026-08-31 12:07:22 +08:00
parent ced71bd8a1
commit 7dceefc55e
8 changed files with 312 additions and 54 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -77,6 +77,17 @@
.lt-srow:last-child{margin-bottom:0;}
.lt-srow .k{width:44px; flex:none; font-size:var(--fs-sm); color:var(--muted);}
.lt-srow .input{width:80px;}
/* 字号步进器:输入框右侧内嵌上下箭头,点击 +1/-1 */
.lt-stepper{position:relative; width:80px; flex:none;}
.lt-stepper .input{width:100%; box-sizing:border-box; padding-right:22px;}
.lt-steparr{position:absolute; right:1px; top:1px; bottom:1px; width:20px;
display:flex; flex-direction:column; border-left:1px solid var(--border);}
.lt-steparr button{flex:1; display:flex; align-items:center; justify-content:center;
border:0; background:transparent; cursor:pointer; color:var(--muted); padding:0;}
.lt-steparr button:hover{background:var(--row-hover); color:var(--text);}
.lt-steparr button:last-child{border-top:1px solid var(--border);}
.lt-steparr button:disabled{opacity:.4; cursor:default; background:transparent; color:var(--muted);}
.lt-steparr svg{width:11px; height:11px; stroke:currentColor; fill:none; stroke-width:2.5;}
.lt-gnote{margin:0; font-size:var(--fs-xs); color:var(--muted); line-height:1.6;}
/* 字体下拉(自包含浮层,每行独立) */
.lt-fontsel{position:relative; flex:1;}
@@ -92,6 +103,11 @@
.lt-tplhint{display:flex; align-items:center; gap:6px; margin-top:12px; padding-top:12px; border-top:1px solid var(--border); font-size:var(--fs-xs); color:var(--muted);}
.lt-tplhint svg{width:14px; height:14px; flex:none; stroke:currentColor; fill:none; stroke-width:2; color:var(--primary);}
.lt-tplhint b{color:var(--primary); font-weight:600;}
/* 默认模板只读锁:提示条 + 受锁区域整体禁用(点「复制」为可编辑逃生口) */
.lt-locknote{margin-top:10px; padding:10px 12px; background:var(--warn-bg); border:1px solid var(--warn); border-radius:var(--r-md); font-size:var(--fs-xs); color:var(--warn); line-height:1.5;}
.lt-lockoff{transition:opacity .15s;}
.dis{opacity:.45; pointer-events:none;}
.lt-panel.locked #fieldList, .lt-panel.locked .lt-adv, .lt-panel.locked #printSec{opacity:.5; pointer-events:none;}
/* 高级折叠 */
.lt-adv{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); overflow:hidden;}
.lt-adv>summary{list-style:none; cursor:pointer; padding:14px 16px; font-size:var(--fs-sm); font-weight:700; color:var(--title); display:flex; align-items:center; gap:8px;}
@@ -116,7 +132,7 @@
<div class="btn ghost sm" onclick="location.href='devices.html'"><svg viewBox="0 0 24 24"><use href="#i-arrow-left"/></svg>返回</div>
<h1>标签模板编辑器</h1><span class="sub">左侧实时预览 · 右侧属性面板</span>
<div class="actions">
<div class="btn ghost" onclick="resetTpl()"><svg viewBox="0 0 24 24"><use href="#i-refresh"/></svg>重置</div>
<div class="btn ghost lt-lockoff" onclick="resetTpl()"><svg viewBox="0 0 24 24"><use href="#i-refresh"/></svg>重置</div>
<div class="btn ghost" onclick="printTpl()"><svg viewBox="0 0 24 24"><use href="#i-printer"/></svg>打印</div>
<div class="btn primary" onclick="toast('模板已保存 ✓')"><svg viewBox="0 0 24 24"><use href="#i-check"/></svg>保存</div>
</div>
@@ -167,10 +183,11 @@
<div class="lt-mgr">
<div class="btn ghost sm" onclick="toast('新建模板(原型)')"><svg viewBox="0 0 24 24"><use href="#i-plus"/></svg>新建</div>
<div class="btn ghost sm" onclick="toast('复制模板(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic27"/></svg>复制</div>
<div class="btn ghost sm" onclick="openRename()"><svg viewBox="0 0 24 24"><use href="#i-edit"/></svg>重命名</div>
<div class="btn ghost sm lt-lockoff" onclick="openRename()"><svg viewBox="0 0 24 24"><use href="#i-edit"/></svg>重命名</div>
<div class="btn ghost sm" onclick="setDefaultTpl()"><svg viewBox="0 0 24 24"><use href="#i-check"/></svg>设默认</div>
<div class="btn danger sm" onclick="toast('删除模板(原型)')"><svg viewBox="0 0 24 24"><use href="#i-close"/></svg>删除</div>
<div class="btn danger sm lt-lockoff" onclick="toast('删除模板(原型)')"><svg viewBox="0 0 24 24"><use href="#i-close"/></svg>删除</div>
</div>
<div class="lt-locknote" id="lockNote">系统默认模板不可修改,点「复制」生成可编辑副本</div>
<div class="lt-tplhint"><svg viewBox="0 0 24 24"><use href="#i-check"/></svg>出签打印使用:<b id="tplActive">默认版式</b></div>
</div>
@@ -186,9 +203,9 @@
<div class="body">
<div class="lt-fmeta" style="margin-bottom:10px">二维码</div>
<div class="lt-grid2">
<div class="field"><label>X</label><input class="input" value="178"></div>
<div class="field"><label>Y</label><input class="input" value="25"></div>
<div class="field"><label>尺寸</label><input class="input" value="134"></div>
<div class="field"><label>X</label><input class="input" value="192"></div>
<div class="field"><label>Y</label><input class="input" value="32"></div>
<div class="field"><label>尺寸</label><input class="input" value="120"></div>
</div>
<div class="lt-fmeta" style="margin:14px 0 10px">条形码</div>
<div class="lt-grid2">
@@ -205,7 +222,7 @@
</details>
<!-- 打印参数(常驻可见) -->
<div class="lt-sec">
<div class="lt-sec" id="printSec">
<div class="lt-sectitle"><svg viewBox="0 0 24 24"><use href="#i-gear"/></svg>打印参数</div>
<div class="lt-grid2">
<div class="field"><label>纸张宽 (mm)</label><input class="input" value="40"></div>
@@ -249,14 +266,12 @@
// 字体不做(热敏单色 MVP);仅「品名」支持自动字号。同物理行两字段共用字号/加粗/对齐(PAIR)。
const ALIGN_LABEL = {left:'左', center:'居中', right:'右'};
const FIELDS = [
{f:'name', label:'品名', type:'text', on:true, auto:true, bold:true, align:'center', size:18},
{f:'shop', label:'店名(抬头)', type:'text', on:true, auto:false, bold:true, align:'center', size:14},
{f:'code', label:'编号', type:'text', on:true, auto:false, bold:false, align:'center', size:14},
{f:'series', label:'型号 / 度数', type:'text', on:true, auto:false, bold:false, align:'center', size:14},
{f:'spec', label:'版本 / 规格', type:'text', on:true, auto:false, bold:false, align:'center', size:14},
{f:'date', label:'生产日期', type:'text', on:true, auto:false, bold:false, align:'center', size:14},
{f:'batch', label:'批次号', type:'text', on:false, auto:false, bold:false, align:'center', size:14},
{f:'remark', label:'备注', type:'text', on:false, auto:false, bold:false, align:'center', size:14},
{f:'name', label:'品名', type:'text', on:true, auto:true, bold:true, align:'center', size:18, sample:'贵州茅台酒'},
{f:'shop', label:'店名(抬头)', type:'text', on:true, auto:false, bold:true, align:'center', size:14, sample:'鼎晟酒行'},
{f:'code', label:'编号', type:'text', on:true, auto:false, bold:false, align:'center', size:14, sample:'P1001'},
{f:'series', label:'型号 / 度数', type:'text', on:true, auto:false, bold:false, align:'center', size:14, sample:'飞天53度'},
{f:'spec', label:'版本 / 规格', type:'text', on:true, auto:false, bold:false, align:'center', size:14, sample:'500ml'},
{f:'date', label:'生产日期', type:'text', on:true, auto:false, bold:false, align:'center', size:14, sample:'2024-05-18'},
{f:'qr', label:'二维码', type:'graphic', on:true},
{f:'barcode', label:'条形码', type:'graphic', on:true},
];
@@ -276,8 +291,17 @@
<button class="switch${fd.auto?' on':''}" onclick="toggleAuto(event,${i})"></button>`
: '';
return `<div class="lt-fbody">
<div class="lt-srow"><span class="k">内容</span>
<input class="input" style="flex:1;width:auto" value="${fd.sample||''}" oninput="setSample(${i},this.value)"></div>
<p class="lt-gnote" style="margin:-4px 0 12px">仅改预览示例,真实打印按商品数据填充</p>
<div class="lt-srow"><span class="k">字号</span>
<input class="input" value="${fd.size}" ${fd.auto?'disabled':''} oninput="setSize(${i},this.value)">
<div class="lt-stepper">
<input class="input" value="${fd.size}" ${fd.auto?'disabled':''} oninput="setSize(${i},this.value)">
<div class="lt-steparr">
<button type="button" ${fd.auto?'disabled':''} onclick="stepSize(${i},1)" aria-label="增大字号"><svg viewBox="0 0 24 24"><use href="#i-chevron-up"/></svg></button>
<button type="button" ${fd.auto?'disabled':''} onclick="stepSize(${i},-1)" aria-label="减小字号"><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></button>
</div>
</div>
${autoCtrl}</div>
<div class="lt-srow"><span class="k">加粗</span>
<button class="switch${fd.bold?' on':''}" onclick="toggleBold(event,${i})"></button></div>
@@ -347,11 +371,27 @@
const n = parseInt(v,10);
if(!isNaN(n)){ FIELDS[i].size = n; applyStyle(i); refreshMeta(i); mirror(i); }
}
/* 内容:仅改左侧预览示例文字,不影响真实打印(真实出签永远取商品数据) */
function setSample(i, v){
FIELDS[i].sample = v;
previewEls(i).forEach(el=>{ el.textContent = v; });
}
/* 步进器 +1/-1:夹在 [6,48],回写输入框并刷新预览 */
function stepSize(i, d){
if(FIELDS[i].auto) return;
let n = parseInt(FIELDS[i].size,10); if(isNaN(n)) n = 14;
n = Math.max(6, Math.min(48, n + d));
FIELDS[i].size = n;
const inp = fieldEl(i).querySelector('.lt-stepper .input');
if(inp) inp.value = n;
applyStyle(i); refreshMeta(i); mirror(i);
}
function toggleAuto(e, i){
e.stopPropagation();
FIELDS[i].auto = !FIELDS[i].auto;
e.currentTarget.classList.toggle('on', FIELDS[i].auto);
fieldEl(i).querySelector('.lt-srow .input').disabled = FIELDS[i].auto;
fieldEl(i).querySelectorAll('.lt-steparr button').forEach(b=>b.disabled = FIELDS[i].auto);
applyStyle(i); refreshMeta(i);
}
function toggleBold(e, i){
@@ -402,8 +442,18 @@
document.getElementById('renameInput').value = name;
document.getElementById('tplPop').classList.remove('show');
renderTplPop();
applyLock();
toast('正在编辑「'+name+'」');
}
/* 默认模板('默认版式')受保护只读:显示锁提示、禁用字段/高级/打印/重命名/删除/重置,
其余(下拉/新建/复制/设默认)保持可用;点「复制」生成可编辑副本 */
function isLockedTpl(){ return tplEditing === '默认版式'; }
function applyLock(){
const on = isLockedTpl();
document.getElementById('lockNote').style.display = on ? '' : 'none';
document.querySelector('.lt-panel').classList.toggle('locked', on);
document.querySelectorAll('.lt-lockoff').forEach(b=>b.classList.toggle('dis', on));
}
function setDefaultTpl(){
tplActive = tplEditing;
document.getElementById('tplActive').textContent = tplActive;
@@ -413,6 +463,7 @@
renderTplPop();
document.addEventListener('click', ()=>document.querySelectorAll('.lt-fontpop.show').forEach(p=>p.classList.remove('show')));
renderFields();
applyLock();
function openRename(){ document.getElementById('ovRename').classList.add('show'); }
function closeRename(){ document.getElementById('ovRename').classList.remove('show'); }
function doRename(){ closeRename(); toast('已重命名 ✓'); }