feat(design): 价签模板编辑器原型(P1),已过审
新增 design/prototype/screens/label-template-editor.html:左实时预览价签 + 右属性面板(模板下拉/显示字段手风琴含每行字体·字号·加粗·对齐/高级区域位置 数字精调/打印参数)。1 期决策:栅格流布局不做拖拽、字段样式内嵌每行、字体仅 系统+内置思源黑体、编辑对象与打印默认对象分离(打印默认徽标+底部提示)。 index.html 登记 kit 卡,devices.html 价签卡编辑入口指向本屏。 check-ds 12 道全绿。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bupi8Kdqkfx2N5acFsHTx5
This commit is contained in:
@@ -146,6 +146,7 @@
|
||||
<a class="kit" href="screens/inventory-check.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-ic61"/></svg></div><div class="kt"><b>库存盘点</b><span>实盘录入 · 差异盈亏</span></div></a>
|
||||
<a class="kit" href="screens/product-detail.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-ic62"/></svg></div><div class="kt"><b>商品详情</b><span>库存分布 · 流水 · 价格历史</span></div></a>
|
||||
<a class="kit" href="screens/devices.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-ic63"/></svg></div><div class="kt"><b>设备管理</b><span>打印机/扫码枪 · 连接状态</span></div></a>
|
||||
<a class="kit" href="screens/label-template-editor.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-ic12"/></svg></div><div class="kt"><b>标签模板编辑器</b><span>实时预览画布 / 属性面板</span></div></a>
|
||||
<a class="kit" href="screens/notifications.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-bell"/></svg></div><div class="kt"><b>通知中心</b><span>列表 · 类型筛选 / 未读</span></div></a>
|
||||
<a class="kit" href="screens/notification.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-bell"/></svg></div><div class="kt"><b>通知详情</b><span>正文 · 关联单据 / 操作</span></div></a>
|
||||
<a class="kit" href="screens/about.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-ic24"/></svg></div><div class="kt"><b>关于我们</b><span>版本 · 更新日志 · 授权</span></div></a>
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
<div class="tpl-desc">40×20mm · 店名 / 品名 / 编号 / 度数规格 / 生产日期 / 二维码 + 底部整幅一维条码</div>
|
||||
<div class="tpl-acts">
|
||||
<div class="btn ghost sm" onclick="openLabelPreview()"><svg viewBox="0 0 24 24"><use href="#i-eye"/></svg>预览</div>
|
||||
<div class="btn ghost sm" onclick="toast('编辑标签模板(原型)')"><svg viewBox="0 0 24 24"><use href="#i-edit"/></svg>编辑</div>
|
||||
<div class="btn ghost sm" onclick="location.href='label-template-editor.html'"><svg viewBox="0 0 24 24"><use href="#i-edit"/></svg>编辑</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,439 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" data-theme="a">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>标签模板编辑器 · 岩美酒库</title>
|
||||
<link rel="stylesheet" href="../tokens.css">
|
||||
<link rel="stylesheet" href="../atoms.css">
|
||||
<style>
|
||||
body{margin:0; height:100vh; overflow:hidden; font-family:var(--font); color:var(--text); background:var(--bg); transition:background-color .28s;}
|
||||
.app{height:100vh; display:flex; flex-direction:column;}
|
||||
.main{padding:22px 26px; overflow:hidden; display:flex; flex-direction:column;}
|
||||
.head{display:flex; align-items:flex-end; gap:14px; margin-bottom:18px; flex:none;}
|
||||
.head h1{margin:0; font-size:var(--fs-h1); font-weight:700; color:var(--title); letter-spacing:.3px;}
|
||||
.head .sub{font-size:var(--fs-sm); color:var(--muted); margin-bottom:2px;}
|
||||
.head .actions{margin-left:auto; display:flex; gap:10px;}
|
||||
|
||||
/* ── 双栏:左预览 · 右属性 ── */
|
||||
.lt-wrap{flex:1; display:grid; grid-template-columns:minmax(0,1fr) 400px; gap:22px; min-height:0;}
|
||||
|
||||
/* 左 · 实时预览舞台 */
|
||||
.lt-stage{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); box-shadow:var(--sh-1); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:18px; padding:24px; min-height:0;}
|
||||
.lt-stitle{align-self:flex-start; font-size:var(--fs-sm); font-weight:600; color:var(--muted); letter-spacing:.4px;}
|
||||
/* 舞台预留 100% 尺寸(320×160 @1.5=480×240);缩放靠 label 自身 transform */
|
||||
.lt-frame{width:480px; height:240px; display:flex; align-items:center; justify-content:center;}
|
||||
/* 价签纸面 320×160(40×20mm@203dpi):flex 栅格流,内容自动填满整纸,删字段自动回流补位 */
|
||||
.lt-label{width:320px; height:160px; background:#fff; border-radius:var(--r-sm); box-shadow:var(--sh-2); overflow:hidden; transform:scale(1.5); transform-origin:center; transition:transform .18s;
|
||||
display:flex; flex-direction:column; box-sizing:border-box;}
|
||||
/* 行①店名 band(flex:none,关掉后整体上填) */
|
||||
.lt-hd{flex:none; padding:4px 12px; text-align:center;
|
||||
background:#1f2a3a; /* ds-allow 价签抬头深蓝(物理印刷色,非主题) */ }
|
||||
.lt-shop{font-size:14px; font-weight:700; letter-spacing:.5px;
|
||||
color:#fff; /* ds-allow 抬头白字 */ }
|
||||
/* 行②主体(flex:1 撑满抬头以下全部高):左列(文字+条码) + 右列(整高二维码) */
|
||||
.lt-main{flex:1; min-height:0; display:flex; align-items:stretch; gap:10px; padding:8px 12px;}
|
||||
/* 左列:文字栏在上、条码在下,条码与文字同宽 */
|
||||
.lt-left{flex:1; min-width:0; display:flex; flex-direction:column; gap:6px;}
|
||||
.lt-txt{flex:1; min-height:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px; text-align:center;}
|
||||
.lt-name{font-size:18px; font-weight:700; line-height:1.15;
|
||||
color:#1a1a1a; /* ds-allow 价签品名墨色 */ }
|
||||
.lt-sub{font-size:14px; line-height:1.15;
|
||||
color:#3a3a3a; /* ds-allow 价签副字段灰 */ }
|
||||
/* 二维码:正方形,撑满整个主体高度(抬头以下一整列,横跨文字+条码) */
|
||||
.lt-qr{flex:none; align-self:stretch; aspect-ratio:1; border-radius:var(--r-sm);
|
||||
background:repeating-conic-gradient(#000 0 25%, #fff 0 50%) 0 0/12px 12px; /* ds-allow 二维码占位棋盘 */ }
|
||||
/* 条码 band(flex:none,宽度=左列宽=文字宽;关掉后左列上填) */
|
||||
.lt-foot{flex:none;}
|
||||
.lt-bars{width:100%; height:30px;
|
||||
background:repeating-linear-gradient(90deg, #000 0 2px, #fff 2px 5px, #000 5px 6px, #fff 6px 10px); /* ds-allow 一维条码占位 */ }
|
||||
.lt-off{display:none !important;}
|
||||
.lt-cap{font-size:var(--fs-xs); color:var(--muted); letter-spacing:.4px;}
|
||||
.lt-tools{display:flex; align-items:center; gap:12px;}
|
||||
.lt-tools .lbl{font-size:var(--fs-xs); color:var(--muted);}
|
||||
|
||||
/* 右 · 属性面板 */
|
||||
.lt-panel{overflow-y:auto; padding-right:4px; display:flex; flex-direction:column; gap:14px;}
|
||||
.lt-sec{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); padding:16px 16px 18px;}
|
||||
.lt-sectitle{font-size:var(--fs-sm); font-weight:700; color:var(--title); margin:0 0 12px; display:flex; align-items:center; gap:8px;}
|
||||
.lt-sectitle svg{width:16px; height:16px; stroke:currentColor; fill:none; stroke-width:2; color:var(--primary);}
|
||||
.lt-mgr{display:flex; flex-wrap:wrap; gap:8px; margin-top:10px;}
|
||||
/* 字段清单(每行内嵌样式,手风琴展开一行) */
|
||||
.lt-field{border-radius:var(--r-md);}
|
||||
.lt-field + .lt-field{margin-top:2px;}
|
||||
.lt-frow{display:flex; align-items:center; gap:10px; padding:9px 8px; border-radius:var(--r-md); cursor:pointer; transition:background .15s;}
|
||||
.lt-frow:hover{background:var(--row-hover);}
|
||||
.lt-field.open>.lt-frow{background:var(--accent-soft);}
|
||||
.lt-fname{flex:1; font-size:var(--fs-body); color:var(--text);}
|
||||
.lt-fmeta{font-size:var(--fs-xs); color:var(--muted);}
|
||||
.lt-frow .switch{flex:none;}
|
||||
.lt-frow .chev{width:16px; height:16px; flex:none; stroke:currentColor; fill:none; stroke-width:2; color:var(--muted); transition:transform .18s;}
|
||||
.lt-field.open>.lt-frow .chev{transform:rotate(180deg);}
|
||||
.lt-field.disabled .lt-fname{color:var(--muted); text-decoration:line-through;}
|
||||
/* 折叠体:默认收起,展开显字段样式 */
|
||||
.lt-fbody{display:none; padding:6px 8px 14px;}
|
||||
.lt-field.open>.lt-fbody{display:block;}
|
||||
.lt-srow{display:flex; align-items:center; gap:12px; margin-bottom:12px;}
|
||||
.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;}
|
||||
.lt-gnote{margin:0; font-size:var(--fs-xs); color:var(--muted); line-height:1.6;}
|
||||
/* 字体下拉(自包含浮层,每行独立) */
|
||||
.lt-fontsel{position:relative; flex:1;}
|
||||
.lt-fontsel .ctrl{width:100%; box-sizing:border-box;}
|
||||
.lt-fontname{flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
|
||||
.lt-fontpop{position:absolute; left:0; top:calc(100% + 4px); width:100%; box-sizing:border-box; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md); box-shadow:var(--sh-3); padding:4px; display:none; z-index:var(--z-pop);}
|
||||
.lt-fontpop.show{display:block;}
|
||||
.lt-fontitem{display:flex; align-items:center; padding:8px 10px; border-radius:var(--r-sm); font-size:var(--fs-body); color:var(--text); cursor:pointer;}
|
||||
.lt-fontitem:hover{background:var(--row-hover);}
|
||||
.lt-fontitem.on{color:var(--primary); font-weight:600;}
|
||||
/* 「打印默认」徽标 + 提示 */
|
||||
.lt-tag{margin-left:auto; font-size:var(--fs-xs); font-weight:600; color:var(--primary); background:var(--accent-soft); border-radius:var(--r-pill); padding:1px 8px;}
|
||||
.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-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;}
|
||||
.lt-adv>summary::-webkit-details-marker{display:none;}
|
||||
.lt-adv>summary .chev{margin-left:auto; width:16px; height:16px; stroke:currentColor; fill:none; stroke-width:2; color:var(--muted); transition:transform .18s;}
|
||||
.lt-adv[open]>summary .chev{transform:rotate(180deg);}
|
||||
.lt-adv .body{padding:0 16px 16px;}
|
||||
.lt-grid2{display:grid; grid-template-columns:1fr 1fr; gap:10px 14px;}
|
||||
|
||||
.ds-fab{position:fixed; right:22px; bottom:22px; width:44px; height:44px; border-radius:var(--r-pill); background:var(--surface); color:var(--muted); border:1px solid var(--border); display:flex; align-items:center; justify-content:center; box-shadow:var(--sh-2); z-index:var(--z-fab); opacity:.6; transition:opacity .18s,color .18s,border-color .18s,transform .18s,box-shadow .18s; text-decoration:none;}
|
||||
.ds-fab:hover{opacity:1; color:var(--primary); border-color:var(--primary); box-shadow:var(--sh-3); transform:translateY(-2px);}
|
||||
.ds-fab svg{width:20px; height:20px; stroke-width:2; fill:none; stroke:currentColor;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<div class="top" data-shell-top data-active="devices"></div>
|
||||
<div class="body">
|
||||
<div class="side" id="side"></div>
|
||||
<div class="main">
|
||||
<div class="head">
|
||||
<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 primary" onclick="toast('模板已保存 ✓')"><svg viewBox="0 0 24 24"><use href="#i-check"/></svg>保存</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lt-wrap">
|
||||
<!-- 左 · 实时预览 -->
|
||||
<div class="lt-stage">
|
||||
<div class="lt-stitle">实时预览</div>
|
||||
<div class="lt-frame">
|
||||
<div class="lt-label" id="label">
|
||||
<div class="lt-hd" data-f="shop"><span class="lt-shop">鼎晟酒行</span></div>
|
||||
<div class="lt-main">
|
||||
<div class="lt-left">
|
||||
<div class="lt-txt">
|
||||
<div class="lt-name" data-f="name">贵州茅台酒</div>
|
||||
<div class="lt-sub"><span data-f="code">P1001</span> <span data-f="series">飞天53度</span></div>
|
||||
<div class="lt-sub"><span data-f="spec">500ml</span> <span data-f="date">2024-05-18</span></div>
|
||||
</div>
|
||||
<div class="lt-foot" data-f="barcode"><div class="lt-bars"></div></div>
|
||||
</div>
|
||||
<div class="lt-qr" data-f="qr"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-tools">
|
||||
<span class="lbl">缩放</span>
|
||||
<div class="seg" id="zoom">
|
||||
<button onclick="setZoom(1.125,this)">75%</button>
|
||||
<button class="on" onclick="setZoom(1.5,this)">100%</button>
|
||||
<button onclick="setZoom(1.875,this)">125%</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-cap">40 × 20 mm · 203 dpi · 示例数据</div>
|
||||
</div>
|
||||
|
||||
<!-- 右 · 属性面板 -->
|
||||
<div class="lt-panel">
|
||||
<!-- 模板管理 -->
|
||||
<div class="lt-sec">
|
||||
<div class="lt-sectitle"><svg viewBox="0 0 24 24"><use href="#i-ic12"/></svg>模板</div>
|
||||
<div class="field">
|
||||
<div class="lt-fontsel">
|
||||
<div class="ctrl" onclick="toggleTpl(event)"><span class="lt-fontname" id="tplName">默认版式</span><svg width="14" height="14" viewBox="0 0 24 24" stroke-width="2"><use href="#i-chevron-down"/></svg></div>
|
||||
<div class="lt-fontpop" id="tplPop"></div>
|
||||
</div>
|
||||
</div>
|
||||
<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" 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>
|
||||
<div class="lt-tplhint"><svg viewBox="0 0 24 24"><use href="#i-check"/></svg>出签打印使用:<b id="tplActive">默认版式</b></div>
|
||||
</div>
|
||||
|
||||
<!-- 显示字段(样式内嵌每行,选中展开折叠) -->
|
||||
<div class="lt-sec">
|
||||
<div class="lt-sectitle"><svg viewBox="0 0 24 24"><use href="#i-eye"/></svg>显示字段<span class="lt-fmeta" style="margin-left:auto;font-weight:400">点字段名展开样式</span></div>
|
||||
<div id="fieldList"></div>
|
||||
</div>
|
||||
|
||||
<!-- 高级 · 区域位置 -->
|
||||
<details class="lt-adv">
|
||||
<summary><svg viewBox="0 0 24 24" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2"><use href="#i-ic12"/></svg>高级 · 区域位置<svg class="chev" viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></summary>
|
||||
<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>
|
||||
<div class="lt-fmeta" style="margin:14px 0 10px">条形码</div>
|
||||
<div class="lt-grid2">
|
||||
<div class="field"><label>X</label><input class="input" value="8"></div>
|
||||
<div class="field"><label>Y</label><input class="input" value="114"></div>
|
||||
<div class="field"><label>宽</label><input class="input" value="164"></div>
|
||||
<div class="field"><label>高</label><input class="input" value="40"></div>
|
||||
</div>
|
||||
<div class="lt-fmeta" style="margin:14px 0 10px">抬头</div>
|
||||
<div class="lt-grid2">
|
||||
<div class="field"><label>高度</label><input class="input" value="24"></div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- 打印参数(常驻可见) -->
|
||||
<div class="lt-sec">
|
||||
<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>
|
||||
<div class="field"><label>纸张高 (mm)</label><input class="input" value="20"></div>
|
||||
<div class="field"><label>DPI</label><input class="input" value="203"></div>
|
||||
<div class="field"><label>间隔 (mm)</label><input class="input" value="2"></div>
|
||||
<div class="field"><label>打印密度</label><input class="input" value="10"></div>
|
||||
<div class="field"><label>打印速度</label><input class="input" value="2"></div>
|
||||
<div class="field"><label>份数</label><input class="input" value="1"></div>
|
||||
</div>
|
||||
<div class="lt-srow" style="margin-top:14px">
|
||||
<span class="k">打印方向</span>
|
||||
<div class="seg" id="dir">
|
||||
<button class="on" onclick="pickSeg(this)">正向</button>
|
||||
<button onclick="pickSeg(this)">反向</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 重命名弹窗 -->
|
||||
<div class="overlay" id="ovRename"><div class="modal" style="width:420px">
|
||||
<div class="modal-head"><h3>重命名模板</h3><svg class="x" viewBox="0 0 24 24" stroke-width="2" onclick="closeRename()"><use href="#i-close"/></svg></div>
|
||||
<div class="modal-body">
|
||||
<div class="frow"><div class="field"><label>模板名称</label><div class="ctrl input-like"><input id="renameInput" value="默认版式"></div></div></div>
|
||||
</div>
|
||||
<div class="modal-foot"><div class="btn ghost" onclick="closeRename()">取消</div><div class="btn primary" onclick="doRename()">保存</div></div>
|
||||
</div></div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script src="shell.js"></script>
|
||||
<script>
|
||||
/* ── 字段清单:样式内嵌每行,手风琴展开 ── */
|
||||
/* 1 期字体:仅系统字体 + 应用已内置思源黑体,不打包新字体(宋体已撤)。
|
||||
楷体/等宽蹭系统,缺失回退默认。 */
|
||||
const FONTS = [
|
||||
['系统默认', ''],
|
||||
['思源黑体(内置)', 'sans-serif'],
|
||||
['楷体', 'KaiTi, STKaiti, serif'],
|
||||
['等宽数字', 'ui-monospace, monospace'],
|
||||
];
|
||||
const ALIGN_LABEL = {left:'左', center:'居中', right:'右'};
|
||||
const FIELDS = [
|
||||
{f:'name', label:'品名', type:'text', on:true, auto:true, bold:true, align:'center', font:0, size:18},
|
||||
{f:'shop', label:'店名(抬头)', type:'text', on:true, auto:false, bold:true, align:'center', font:0, size:14},
|
||||
{f:'code', label:'编号', type:'text', on:true, auto:false, bold:false, align:'center', font:0, size:14},
|
||||
{f:'series', label:'型号 / 度数', type:'text', on:true, auto:false, bold:false, align:'center', font:0, size:14},
|
||||
{f:'spec', label:'版本 / 规格', type:'text', on:true, auto:false, bold:false, align:'center', font:0, size:14},
|
||||
{f:'date', label:'生产日期', type:'text', on:true, auto:false, bold:false, align:'center', font:0, size:14},
|
||||
{f:'batch', label:'批次号', type:'text', on:false, auto:false, bold:false, align:'center', font:0, size:14},
|
||||
{f:'remark', label:'备注', type:'text', on:false, auto:false, bold:false, align:'center', font:0, size:14},
|
||||
{f:'qr', label:'二维码', type:'graphic', on:true},
|
||||
{f:'barcode', label:'条形码', type:'graphic', on:true},
|
||||
];
|
||||
|
||||
function metaText(fd){
|
||||
if(fd.type==='graphic') return '图形 · 位置见高级';
|
||||
const parts = [fd.auto ? '自动字号' : fd.size+'px'];
|
||||
if(fd.bold) parts.push('加粗');
|
||||
parts.push(ALIGN_LABEL[fd.align]);
|
||||
return parts.join(' · ');
|
||||
}
|
||||
function fontBody(fd, i){
|
||||
const opts = FONTS.map((ft,fi)=>
|
||||
`<div class="lt-fontitem${fi===fd.font?' on':''}" onclick="pickFont(event,${i},${fi})">${ft[0]}</div>`).join('');
|
||||
return `<div class="lt-fbody">
|
||||
<div class="lt-srow"><span class="k">字体</span>
|
||||
<div class="lt-fontsel">
|
||||
<div class="ctrl" onclick="toggleFont(event,${i})"><span class="lt-fontname">${FONTS[fd.font][0]}</span><svg width="14" height="14" viewBox="0 0 24 24" stroke-width="2"><use href="#i-chevron-down"/></svg></div>
|
||||
<div class="lt-fontpop">${opts}</div>
|
||||
</div></div>
|
||||
<div class="lt-srow"><span class="k">字号</span>
|
||||
<input class="input" value="${fd.size}" ${fd.auto?'disabled':''} oninput="setSize(${i},this.value)">
|
||||
<span class="lt-fmeta">自动</span>
|
||||
<button class="switch${fd.auto?' on':''}" onclick="toggleAuto(event,${i})"></button></div>
|
||||
<div class="lt-srow"><span class="k">加粗</span>
|
||||
<button class="switch${fd.bold?' on':''}" onclick="toggleBold(event,${i})"></button></div>
|
||||
<div class="lt-srow"><span class="k">对齐</span>
|
||||
<div class="seg">
|
||||
<button class="${fd.align==='left'?'on':''}" onclick="setAlign(event,${i},'left')">左</button>
|
||||
<button class="${fd.align==='center'?'on':''}" onclick="setAlign(event,${i},'center')">居中</button>
|
||||
<button class="${fd.align==='right'?'on':''}" onclick="setAlign(event,${i},'right')">右</button>
|
||||
</div></div>
|
||||
</div>`;
|
||||
}
|
||||
function graphicBody(fd){
|
||||
return `<div class="lt-fbody"><p class="lt-gnote">图形字段,无文字样式。位置与尺寸在下方「高级 · 区域位置」中调整。</p></div>`;
|
||||
}
|
||||
function rowHtml(fd, i){
|
||||
return `<div class="lt-field${fd.on?'':' disabled'}" data-i="${i}">
|
||||
<div class="lt-frow" onclick="expandField(${i})">
|
||||
<button class="switch${fd.on?' on':''}" onclick="tglField(event,${i})"></button>
|
||||
<span class="lt-fname">${fd.label}</span>
|
||||
<span class="lt-fmeta">${metaText(fd)}</span>
|
||||
<svg class="chev" viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg>
|
||||
</div>
|
||||
${fd.type==='graphic' ? graphicBody(fd) : fontBody(fd, i)}
|
||||
</div>`;
|
||||
}
|
||||
function renderFields(){
|
||||
document.getElementById('fieldList').innerHTML = FIELDS.map(rowHtml).join('');
|
||||
FIELDS.forEach((_,i)=>applyStyle(i));
|
||||
}
|
||||
function fieldEl(i){ return document.querySelector('.lt-field[data-i="'+i+'"]'); }
|
||||
function previewEls(i){ return document.querySelectorAll('.lt-label [data-f="'+FIELDS[i].f+'"]'); }
|
||||
function refreshMeta(i){ fieldEl(i).querySelector('.lt-fmeta').textContent = metaText(FIELDS[i]); }
|
||||
|
||||
function applyStyle(i){
|
||||
const fd = FIELDS[i];
|
||||
previewEls(i).forEach(el=>{
|
||||
el.classList.toggle('lt-off', !fd.on);
|
||||
if(fd.type==='text'){
|
||||
el.style.fontFamily = FONTS[fd.font][1];
|
||||
el.style.fontWeight = fd.bold ? '700' : '400';
|
||||
el.style.textAlign = fd.align;
|
||||
el.style.fontSize = fd.auto ? '' : fd.size+'px';
|
||||
}
|
||||
});
|
||||
}
|
||||
/* 手风琴:一次只展开一行 */
|
||||
function expandField(i){
|
||||
const el = fieldEl(i), wasOpen = el.classList.contains('open');
|
||||
document.querySelectorAll('.lt-field.open').forEach(f=>f.classList.remove('open'));
|
||||
if(!wasOpen) el.classList.add('open');
|
||||
}
|
||||
function tglField(e, i){
|
||||
e.stopPropagation();
|
||||
FIELDS[i].on = !FIELDS[i].on;
|
||||
e.currentTarget.classList.toggle('on', FIELDS[i].on);
|
||||
fieldEl(i).classList.toggle('disabled', !FIELDS[i].on);
|
||||
applyStyle(i);
|
||||
}
|
||||
function toggleFont(e, i){
|
||||
e.stopPropagation();
|
||||
const pop = fieldEl(i).querySelector('.lt-fontpop'), open = pop.classList.contains('show');
|
||||
document.querySelectorAll('.lt-fontpop.show').forEach(p=>p.classList.remove('show'));
|
||||
if(!open) pop.classList.add('show');
|
||||
}
|
||||
function pickFont(e, i, fi){
|
||||
e.stopPropagation();
|
||||
FIELDS[i].font = fi;
|
||||
const el = fieldEl(i);
|
||||
el.querySelectorAll('.lt-fontitem').forEach((it,x)=>it.classList.toggle('on', x===fi));
|
||||
el.querySelector('.lt-fontname').textContent = FONTS[fi][0];
|
||||
el.querySelector('.lt-fontpop').classList.remove('show');
|
||||
applyStyle(i);
|
||||
}
|
||||
function setSize(i, v){
|
||||
const n = parseInt(v,10);
|
||||
if(!isNaN(n)){ FIELDS[i].size = n; applyStyle(i); refreshMeta(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;
|
||||
applyStyle(i); refreshMeta(i);
|
||||
}
|
||||
function toggleBold(e, i){
|
||||
e.stopPropagation();
|
||||
FIELDS[i].bold = !FIELDS[i].bold;
|
||||
e.currentTarget.classList.toggle('on', FIELDS[i].bold);
|
||||
applyStyle(i); refreshMeta(i);
|
||||
}
|
||||
function setAlign(e, i, a){
|
||||
e.stopPropagation();
|
||||
FIELDS[i].align = a;
|
||||
e.currentTarget.parentElement.querySelectorAll('button').forEach(b=>b.classList.remove('on'));
|
||||
e.currentTarget.classList.add('on');
|
||||
applyStyle(i); refreshMeta(i);
|
||||
}
|
||||
|
||||
function pickSeg(btn){
|
||||
btn.parentElement.querySelectorAll('button').forEach(b=>b.classList.remove('on'));
|
||||
btn.classList.add('on');
|
||||
}
|
||||
function setZoom(z, btn){
|
||||
btn.parentElement.querySelectorAll('button').forEach(b=>b.classList.remove('on'));
|
||||
btn.classList.add('on');
|
||||
document.getElementById('label').style.transform = 'scale('+z+')';
|
||||
}
|
||||
function resetTpl(){ toast('已重置为默认版式'); }
|
||||
/* 模板:下拉选=正在编辑;「设默认」=打印实际使用的那个(二者可不同) */
|
||||
const TPLS = ['默认版式', '大字号简约', '双语抬头'];
|
||||
let tplEditing = '默认版式'; // 正在编辑
|
||||
let tplActive = '默认版式'; // 出签打印默认
|
||||
function renderTplPop(){
|
||||
document.getElementById('tplPop').innerHTML = TPLS.map(n=>
|
||||
`<div class="lt-fontitem${n===tplEditing?' on':''}" onclick="pickTpl(event,'${n}')">${n}${n===tplActive?'<span class="lt-tag">打印默认</span>':''}</div>`
|
||||
).join('');
|
||||
}
|
||||
function toggleTpl(e){
|
||||
e.stopPropagation();
|
||||
const p = document.getElementById('tplPop'), open = p.classList.contains('show');
|
||||
document.querySelectorAll('.lt-fontpop.show').forEach(x=>x.classList.remove('show'));
|
||||
if(!open) p.classList.add('show');
|
||||
}
|
||||
function pickTpl(e, name){
|
||||
e.stopPropagation();
|
||||
tplEditing = name;
|
||||
document.getElementById('tplName').textContent = name;
|
||||
document.getElementById('renameInput').value = name;
|
||||
document.getElementById('tplPop').classList.remove('show');
|
||||
renderTplPop();
|
||||
toast('正在编辑「'+name+'」');
|
||||
}
|
||||
function setDefaultTpl(){
|
||||
tplActive = tplEditing;
|
||||
document.getElementById('tplActive').textContent = tplActive;
|
||||
renderTplPop();
|
||||
toast('已设为打印默认:'+tplActive);
|
||||
}
|
||||
renderTplPop();
|
||||
document.addEventListener('click', ()=>document.querySelectorAll('.lt-fontpop.show').forEach(p=>p.classList.remove('show')));
|
||||
renderFields();
|
||||
function openRename(){ document.getElementById('ovRename').classList.add('show'); }
|
||||
function closeRename(){ document.getElementById('ovRename').classList.remove('show'); }
|
||||
function doRename(){ closeRename(); toast('已重命名 ✓'); }
|
||||
document.addEventListener('keydown',e=>{ if(e.key==='Escape'){ closeRename(); closeMenus(); } });
|
||||
</script>
|
||||
<a class="ds-fab" href="../index.html" title="返回设计系统" aria-label="返回设计系统"><svg viewBox="0 0 24 24"><use href="#i-arrow-left"/></svg></a>
|
||||
<script src="fab.js"></script>
|
||||
<script src="notify.js"></script>
|
||||
<script src="statusbar.js"></script>
|
||||
<script src="icons.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user