rollcall-classroom-assets 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dm-app.js +78 -7
- package/package.json +1 -1
- package/zuoye-app.js +59 -11
package/dm-app.js
CHANGED
|
@@ -143,6 +143,14 @@ const lpPass = document.getElementById('lpPass');
|
|
|
143
143
|
const lpFail = document.getElementById('lpFail');
|
|
144
144
|
|
|
145
145
|
// --- 班级按钮(按当前账号任教班级动态生成) ---
|
|
146
|
+
function obSessionBar() { // 会话过期横幅:SW 喂了陈旧副本时,别让老师对着绿界面干点不动
|
|
147
|
+
if (document.getElementById('obSessionBar')) return;
|
|
148
|
+
const b = document.createElement('div');
|
|
149
|
+
b.id = 'obSessionBar';
|
|
150
|
+
b.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:9999;background:#8a2b0f;color:#fff;text-align:center;font-size:13px;padding:5px 10px;';
|
|
151
|
+
b.innerHTML = '登录已过期,保存不会生效 —— <a href="/login.html" style="color:#fff;text-decoration:underline">点此重新登录</a>';
|
|
152
|
+
(document.body || document.documentElement).appendChild(b);
|
|
153
|
+
}
|
|
146
154
|
function buildClassBtns() {
|
|
147
155
|
classBar.querySelectorAll('.boot-tip').forEach(e => e.remove()); // 移除首屏「正在加载名单…」占位
|
|
148
156
|
classBar.querySelectorAll('.class-btn.class-cls').forEach(b => b.remove());
|
|
@@ -1054,23 +1062,66 @@ function updateSyncIndicator() {
|
|
|
1054
1062
|
}
|
|
1055
1063
|
|
|
1056
1064
|
// 批量同步所有待处理记录
|
|
1065
|
+
// —— 攒批上报(2026-10-14 容量整改)——
|
|
1066
|
+
// 批量接口早就有了(record-qa-batch 一次收一整批),但每个 ✓/✗ 都**立刻**触发一次发送:
|
|
1067
|
+
// 一节 53 人的课 = 53 次跨境往返(实测单次 TTFB 0.8~1.5 秒),也是全站最容易省的请求量。
|
|
1068
|
+
// 现在:攒够 QA_FLUSH_N 条或等 QA_FLUSH_MS 毫秒发一批;切后台/关页面**立即冲刷**,
|
|
1069
|
+
// 绝不为了攒批把老师的点名结果留在 localStorage 里。服务端按 class+name+result+time 去重,
|
|
1070
|
+
// 所以冲刷后没读到响应(页面被冻结)也不要紧 —— 下次打开重发同一批,不会产生重复记录。
|
|
1071
|
+
const QA_FLUSH_N = 5, QA_FLUSH_MS = 3000;
|
|
1072
|
+
let qaFlushTimer = null, qaSending = false, qaDeferredFlush = false;
|
|
1073
|
+
function requestQASync() {
|
|
1074
|
+
const n = getPendingQAs().length;
|
|
1075
|
+
if (n === 0) return;
|
|
1076
|
+
if (n >= QA_FLUSH_N) { flushQASync(); return; }
|
|
1077
|
+
if (!qaFlushTimer) qaFlushTimer = setTimeout(() => { qaFlushTimer = null; flushQASync(); }, QA_FLUSH_MS);
|
|
1078
|
+
}
|
|
1079
|
+
function flushQASync() {
|
|
1080
|
+
if (qaFlushTimer) { clearTimeout(qaFlushTimer); qaFlushTimer = null; }
|
|
1081
|
+
if (qaSending) { qaDeferredFlush = true; return; } // 记一下:上一批落下来时补发,别把这次请求吞了
|
|
1082
|
+
syncPendingQAs();
|
|
1083
|
+
}
|
|
1084
|
+
// 切后台/关页面前冲刷。★ visibilitychange 挂在 **document** 上(规范里它在 document 触发,
|
|
1085
|
+
// 而且不冒泡)—— 挂在 window 上收不到,测试第一版就是这么漏掉的;pagehide 才在 window。
|
|
1086
|
+
// 走 flushQASync 而不是直接调 syncPendingQAs:上一批还在飞时后者会被发送闸挡掉,
|
|
1087
|
+
// 挡掉又不能不管 —— 这正是「切后台了却什么都没发出去」的那条路。
|
|
1088
|
+
try {
|
|
1089
|
+
document.addEventListener('visibilitychange', () => {
|
|
1090
|
+
if (!document.hidden) return;
|
|
1091
|
+
if (getPendingQAs().length > 0) flushQASync();
|
|
1092
|
+
}, { passive: true });
|
|
1093
|
+
} catch (e) { /* 老环境没有这个事件,不阻塞点名 */ }
|
|
1094
|
+
try {
|
|
1095
|
+
window.addEventListener('pagehide', () => {
|
|
1096
|
+
if (getPendingQAs().length > 0) flushQASync();
|
|
1097
|
+
}, { passive: true });
|
|
1098
|
+
} catch (e) { }
|
|
1099
|
+
|
|
1057
1100
|
async function syncPendingQAs() {
|
|
1058
1101
|
let pending = getPendingQAs();
|
|
1059
1102
|
if (pending.length === 0) return;
|
|
1103
|
+
if (qaSending) return;
|
|
1104
|
+
qaSending = true;
|
|
1105
|
+
setTimeout(() => { qaSending = false; }, 8000); // 兜底:响应回调丢了也别把发送闸永久锁住
|
|
1060
1106
|
// 收集后通过批量 API 一次性提交
|
|
1061
1107
|
const batch = pending.map(p => ({ class: p.class, name: p.name, result: p.result, time: p.time, ...(p.content ? { content: p.content } : {}) }));
|
|
1062
1108
|
try {
|
|
1063
1109
|
const r = await fetch('/api/record-qa-batch', {
|
|
1064
1110
|
method: 'POST',
|
|
1065
1111
|
headers: { 'Content-Type': 'application/json' },
|
|
1112
|
+
// keepalive:切后台/关页面那一次的冲刷要能在页面被冻结后仍然发出去
|
|
1113
|
+
keepalive: true,
|
|
1066
1114
|
body: JSON.stringify(batch)
|
|
1067
1115
|
});
|
|
1068
1116
|
const data = await r.json();
|
|
1069
1117
|
if (data.ok) {
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1072
|
-
|
|
1073
|
-
|
|
1118
|
+
// 只从队列里摘掉【这一批发出去的】那些条目 —— 必须重新读一遍当前队列。
|
|
1119
|
+
// 以前是拿进来时那份 pending 快照算 remaining 再整体写回:上一批在飞的这几百毫秒里
|
|
1120
|
+
// 老师新点的记录(不在快照里)会被这次写回一起清掉,本机、服务器两头都没有 —— 静默丢点名结果。
|
|
1121
|
+
// 服务端按 class+name+result+time 去重,saved 为实际新增数;即便 saved=0(全部已存在)
|
|
1122
|
+
// 也应摘除,避免无限重试。
|
|
1123
|
+
const sentSet = new Set(batch.map(e => `${e.class}|${e.name}|${e.result}|${e.time}`));
|
|
1124
|
+
const remaining = getPendingQAs().filter(p => !sentSet.has(`${p.class}|${p.name}|${p.result}|${p.time}`));
|
|
1074
1125
|
savePendingQAs(remaining);
|
|
1075
1126
|
if (remaining.length === 0) {
|
|
1076
1127
|
if (data.saved > 0) {
|
|
@@ -1080,6 +1131,9 @@ async function syncPendingQAs() {
|
|
|
1080
1131
|
} else {
|
|
1081
1132
|
delToastText.textContent = `⚠️ 同步 ${data.saved} 条,剩余 ${remaining.length} 条待重试`;
|
|
1082
1133
|
setTimeout(updateSyncIndicator, 2000);
|
|
1134
|
+
// 送成功的这批落地后,队列里还剩(多半就是上面那几百毫秒里新点的)—— 继续排一波。
|
|
1135
|
+
// 只在成功路径上排:失败已有 10 秒退避,这里无条件重排会在服务端挂掉时变成密集重试。
|
|
1136
|
+
requestQASync();
|
|
1083
1137
|
}
|
|
1084
1138
|
return;
|
|
1085
1139
|
}
|
|
@@ -1088,6 +1142,15 @@ async function syncPendingQAs() {
|
|
|
1088
1142
|
updateSyncIndicator();
|
|
1089
1143
|
// 超时/失败后等 10 秒再试
|
|
1090
1144
|
setTimeout(syncPendingQAs, 10000);
|
|
1145
|
+
} finally {
|
|
1146
|
+
qaSending = false; // 发送闸必须在两条路径上都放开,否则点名会「看起来不再同步」
|
|
1147
|
+
// 上一批在飞时收到的冲刷请求(切后台、满 5 条、改判连点)在这里补一次。
|
|
1148
|
+
// 只在「确实有人请求过」时补 —— 无条件按剩余量重发会绕开上面那个 10 秒退避,
|
|
1149
|
+
// 服务端挂了就会变成密集重试风暴。
|
|
1150
|
+
if (qaDeferredFlush) {
|
|
1151
|
+
qaDeferredFlush = false;
|
|
1152
|
+
if (getPendingQAs().length > 0) syncPendingQAs();
|
|
1153
|
+
}
|
|
1091
1154
|
}
|
|
1092
1155
|
}
|
|
1093
1156
|
|
|
@@ -1105,8 +1168,8 @@ function recordQA(resultType) {
|
|
|
1105
1168
|
delToastText.textContent = `📝 已记录:${name} - ${label}${content ? `(${content})` : ''}`;
|
|
1106
1169
|
setTimeout(() => updateSyncIndicator(), 1500);
|
|
1107
1170
|
|
|
1108
|
-
// 2.
|
|
1109
|
-
|
|
1171
|
+
// 2. 后台同步到服务器(攒批:见 requestQASync)
|
|
1172
|
+
requestQASync();
|
|
1110
1173
|
|
|
1111
1174
|
// 3. 实时名单面板(开着时:本地 pending 即时渲染 + 稍后网络校准)
|
|
1112
1175
|
if (livePanel.classList.contains('show')) {
|
|
@@ -1329,7 +1392,7 @@ function flipQaResult(name, target) {
|
|
|
1329
1392
|
renderLivePanel();
|
|
1330
1393
|
delToastText.textContent = `↔️ 已改判:${name} → ${target === 'correct' ? '✓ 合格' : '✗ 不合格'}`;
|
|
1331
1394
|
setTimeout(() => updateSyncIndicator(), 1500);
|
|
1332
|
-
|
|
1395
|
+
requestQASync(); // 改判也走攒批(连点改判时一次上报)
|
|
1333
1396
|
scheduleLiveRefresh();
|
|
1334
1397
|
}
|
|
1335
1398
|
|
|
@@ -1619,6 +1682,14 @@ async function bootstrap() {
|
|
|
1619
1682
|
} else {
|
|
1620
1683
|
selectClass(myClasses[0]);
|
|
1621
1684
|
}
|
|
1685
|
+
// ⚠️ 会话有效性旁路核对(2026-10-13 P0-6):__BOOT__ 是上次有效会话时注入的快照,
|
|
1686
|
+
// SW 在网络差时可能把它喂给会话已过期的浏览器 —— 界面全绿,保存却全 401。
|
|
1687
|
+
(async () => {
|
|
1688
|
+
try {
|
|
1689
|
+
const r = await fetch('/api/me', { cache: 'no-store' });
|
|
1690
|
+
if (r.status === 401) obSessionBar();
|
|
1691
|
+
} catch (e) { /* 网络不通:离线模式自有兜底 */ }
|
|
1692
|
+
})();
|
|
1622
1693
|
const pending = getPendingQAs();
|
|
1623
1694
|
if (pending.length > 0) setTimeout(syncPendingQAs, 2000);
|
|
1624
1695
|
return; // 服务端注入数据已完整,无需再拉 API
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rollcall-classroom-assets",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Static assets for the classroom workbench (roll-call): app JS, pinyin/xlsx/zxing engines and icons. Published only so they can be served from a China-friendly CDN.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"assets",
|
package/zuoye-app.js
CHANGED
|
@@ -1339,6 +1339,14 @@ function obBadge(n) { // 持久徽标:队列非空就常驻,不像 flashAu
|
|
|
1339
1339
|
}
|
|
1340
1340
|
obChip.textContent = '📴 待补传 ' + n + ' 条';
|
|
1341
1341
|
}
|
|
1342
|
+
function obSessionBar() { // 会话过期横幅:SW 喂了陈旧副本时,别让老师对着绿界面干点不动
|
|
1343
|
+
if (document.getElementById('obSessionBar')) return;
|
|
1344
|
+
const b = document.createElement('div');
|
|
1345
|
+
b.id = 'obSessionBar';
|
|
1346
|
+
b.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:9999;background:#8a2b0f;color:#fff;text-align:center;font-size:13px;padding:5px 10px;';
|
|
1347
|
+
b.innerHTML = '登录已过期,保存不会生效 —— <a href="/login.html" style="color:#fff;text-decoration:underline">点此重新登录</a>';
|
|
1348
|
+
(document.body || document.documentElement).appendChild(b);
|
|
1349
|
+
}
|
|
1342
1350
|
function obOfflineBar() { // 离线模式横幅:开着页面断网时也能一眼看到
|
|
1343
1351
|
let b = document.getElementById('obOfflineBar');
|
|
1344
1352
|
if (b) return;
|
|
@@ -2069,12 +2077,40 @@ function buildDateChip(it){
|
|
|
2069
2077
|
});
|
|
2070
2078
|
return c;
|
|
2071
2079
|
}
|
|
2080
|
+
// —— 历史记录范围(左栏日期芯片往回取几天)——
|
|
2081
|
+
// 服务端只接受 1~7 / 14(其余回落 7)。为什么要窗口:`zuoye_<老师>_<班>_<日期>` 一学期就是几百个键,
|
|
2082
|
+
// 以前 /api/zuoye/history 是「list 全部 + 每 20 个一批 get」,SQL 条数随学期线性增长,
|
|
2083
|
+
// 而免费计划每次 invocation 只允许 50 条 D1 查询(超了直接报错)。
|
|
2084
|
+
// 现在窗口内是一段连续键区间 → 服务端一条范围读拿回全部值(见 src/d1store.ts 的 getRange)。
|
|
2085
|
+
const HIST_DAYS_ALLOWED = [1, 2, 3, 4, 5, 6, 7, 14];
|
|
2086
|
+
let histDays = (() => {
|
|
2087
|
+
let v;
|
|
2088
|
+
try { v = parseInt(localStorage.getItem('zy_hist_days') || '7', 10); } catch (e) { v = 7; }
|
|
2089
|
+
return HIST_DAYS_ALLOWED.indexOf(v) >= 0 ? v : 7;
|
|
2090
|
+
})();
|
|
2091
|
+
function histUrl(cls) {
|
|
2092
|
+
return `/api/zuoye/history?cls=${encodeURIComponent(cls)}&days=${histDays}`;
|
|
2093
|
+
}
|
|
2094
|
+
function wireHistDays() {
|
|
2095
|
+
const sel = $('histDaysSel');
|
|
2096
|
+
if (!sel || sel.__wired) return;
|
|
2097
|
+
sel.__wired = 1;
|
|
2098
|
+
sel.value = String(histDays);
|
|
2099
|
+
sel.onchange = () => {
|
|
2100
|
+
const v = parseInt(sel.value, 10);
|
|
2101
|
+
histDays = HIST_DAYS_ALLOWED.indexOf(v) >= 0 ? v : 7;
|
|
2102
|
+
sel.value = String(histDays);
|
|
2103
|
+
try { localStorage.setItem('zy_hist_days', String(histDays)); } catch (e) { }
|
|
2104
|
+
loadHistory(); // 换范围就立刻重拉,别让老师以为要点两下
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2072
2107
|
async function loadHistory() {
|
|
2108
|
+
wireHistDays();
|
|
2073
2109
|
const h = $('dateList');
|
|
2074
2110
|
h.innerHTML = '<span class="hist-empty">加载历史…</span>';
|
|
2075
2111
|
closeDatePop();
|
|
2076
2112
|
try {
|
|
2077
|
-
const r = await (await fetch(
|
|
2113
|
+
const r = await (await fetch(histUrl(curCls))).json();
|
|
2078
2114
|
histories = r.ok ? (r.dates || []) : [];
|
|
2079
2115
|
} catch(e){ histories = []; }
|
|
2080
2116
|
$('moreDatesBtn').style.display = 'none';
|
|
@@ -3201,11 +3237,11 @@ async function openDatePicker(cls) {
|
|
|
3201
3237
|
const chipsBox = $('dpChips');
|
|
3202
3238
|
const titleEl = $('dpTitle');
|
|
3203
3239
|
if (!overlay || !chipsBox || !titleEl) return;
|
|
3204
|
-
titleEl.textContent = `${cls}班 ·
|
|
3240
|
+
titleEl.textContent = `${cls}班 · 选择日期(近 ${histDays} 天)`;
|
|
3205
3241
|
chipsBox.innerHTML = '<span class="dp-empty">加载历史记录…</span>';
|
|
3206
3242
|
overlay.classList.add('on');
|
|
3207
3243
|
try {
|
|
3208
|
-
const r = await (await fetch(
|
|
3244
|
+
const r = await (await fetch(histUrl(cls))).json();
|
|
3209
3245
|
const dates = r.ok ? (r.dates || []) : [];
|
|
3210
3246
|
if (!dates.length) { chipsBox.innerHTML = '<span class="dp-empty">该班暂无历史记录</span>'; return; }
|
|
3211
3247
|
chipsBox.innerHTML = '';
|
|
@@ -3301,6 +3337,15 @@ async function bootstrap() {
|
|
|
3301
3337
|
saveBtn.disabled = shareBtn.disabled = true;
|
|
3302
3338
|
return;
|
|
3303
3339
|
}
|
|
3340
|
+
// ⚠️ 会话有效性旁路核对(2026-10-13 P0-6):__BOOT__ 是上次**有效会话**时注入的快照,
|
|
3341
|
+
// SW 在网络差时可能把它喂给一个会话已过期的浏览器 —— 界面全绿,保存却全 401。
|
|
3342
|
+
// 不阻塞渲染地并行核对一次:401 就挂横幅明说,别让老师对着一片绿干点不动。
|
|
3343
|
+
(async () => {
|
|
3344
|
+
try {
|
|
3345
|
+
const r = await fetch('/api/me', { cache: 'no-store' });
|
|
3346
|
+
if (r.status === 401) obSessionBar();
|
|
3347
|
+
} catch (e) { /* 网络不通:离线模式自有兜底,不打扰 */ }
|
|
3348
|
+
})();
|
|
3304
3349
|
setEditReady(false);
|
|
3305
3350
|
bookLoad();
|
|
3306
3351
|
const kick = () => schedulePrefetch();
|
|
@@ -8173,7 +8218,7 @@ const BC_MODE_KEY = 'rollcall_bc_mode';
|
|
|
8173
8218
|
const BC_TIP = {
|
|
8174
8219
|
thermal: '编号 = 班级号 + 姓氏拼音 A→Z 序号(35 班第 1 个 = 3501),全班固定不变。<b>热敏 50×20</b>:一张标签占一页,把打印机纸张设成 50×20 后直接连打,撕下贴作业本封面即可。',
|
|
8175
8220
|
'30x10': '编号 = 班级号 + 姓氏拼音 A→Z 序号(35 班第 1 个 = 3501),全班固定不变。<b>热敏 30×10</b>:便携标签机常用小纸,一张占一页;上行印「编号 + 姓名」、下行是条码,把打印机纸张设成 30×10 后直接连打,撕下贴作业本角即可。',
|
|
8176
|
-
a4: '编号 = 班级号 + 姓氏拼音 A→Z 序号(35 班第 1 个 = 3501),全班固定不变。<b>A4 拼版</b
|
|
8221
|
+
a4: '编号 = 班级号 + 姓氏拼音 A→Z 序号(35 班第 1 个 = 3501),全班固定不变。<b>A4 拼版</b>:一页 <b>4 列 × 14 行 = 56 格</b>(一个班 50 出头也能一页印完,不必跨页),打印后沿虚线剪开,贴作业本封面即可用扫码枪扫。',
|
|
8177
8222
|
a4one: '编号 = 班级号 + 姓氏拼音 A→Z 序号(35 班第 1 个 = 3501),全班固定不变。<b>A4 · 一生一版</b>:一个孩子一张纸,11 行 × 3 列 = 33 张 TA 自己的 50×20 标签;<b>条码带已加高到 12.8mm</b>(贴书脊竖着更好扫),格与格之间留 6mm 空当,落剪刀好裁。'
|
|
8178
8223
|
};
|
|
8179
8224
|
// 打印方式白名单:thermal = 50×20、30x10 = 便携小标签、a4 = 拼版、a4one = A4 一生一版(11×4 同名)。
|
|
@@ -8181,11 +8226,13 @@ const BC_TIP = {
|
|
|
8181
8226
|
const BC_MODES = ['thermal', '30x10', 'a4', 'a4one'];
|
|
8182
8227
|
function normBcMode(v) { return BC_MODES.indexOf(String(v)) >= 0 ? String(v) : 'thermal'; }
|
|
8183
8228
|
// @page 是**整篇文档级**规则,只能由 JS 按当前模式注入一条;三模式各自一张纸,集中在这里。
|
|
8184
|
-
// 热敏纸都不吃页边距(标签本身就是成品尺寸),A4
|
|
8229
|
+
// 热敏纸都不吃页边距(标签本身就是成品尺寸),A4 才要留边距走打印机不可打印区。
|
|
8185
8230
|
const BC_PAGE = {
|
|
8186
8231
|
thermal: '@page{size:50mm 20mm;margin:0}',
|
|
8187
8232
|
'30x10': '@page{size:30mm 10mm;margin:0}',
|
|
8188
|
-
|
|
8233
|
+
// 拼版:8mm → 5mm(第 177 轮,用户:一页要装下整个班,最多 53 人)。
|
|
8234
|
+
// 5mm 下可用 200×287mm → 4 列 × 14 行 = 56 格,一页放下整班;原来 8mm/12 行 = 48 格,53 人要跨页。
|
|
8235
|
+
a4: '@page{size:A4 portrait;margin:5mm}',
|
|
8189
8236
|
// 一生一版:5mm 页边距 → 可用宽 200mm = 4×50mm,正好贴满预裁标签纸
|
|
8190
8237
|
a4one: '@page{size:A4 portrait;margin:5mm}'
|
|
8191
8238
|
};
|
|
@@ -8231,12 +8278,13 @@ function renderBcSheet() {
|
|
|
8231
8278
|
// (Code128 下限 0.25mm 的 1.97 倍),左右几乎无白边(静止区只留 2 模块)。
|
|
8232
8279
|
// 上行 3mm + 0.15mm 间距 + 条码 6.15mm ≈ 9.3mm,10mm 纸内不跨页。
|
|
8233
8280
|
// A4 拼版 :姓名 / 条码 / 编号 三行,剪开即用。第 176 轮:**条码不再写死 35mm 宽**,
|
|
8234
|
-
// 改由 CSS width:100% 撑满格子(见 .mode-a4 .bc-svg
|
|
8235
|
-
//
|
|
8236
|
-
//
|
|
8237
|
-
//
|
|
8281
|
+
// 改由 CSS width:100% 撑满格子(见 .mode-a4 .bc-svg),于是出码比例必须同步压扁。
|
|
8282
|
+
// 第 177 轮:一页要装下整班(4 列 × 14 行 = 56 格)→ 行高被压到 ≈18.4mm,
|
|
8283
|
+
// 出码再压一档 h 35 → 30(viewBox 17.5 → 15 模块高):格子内宽 ≈45.3mm 时
|
|
8284
|
+
// 条码高 ≈10.3mm、模块宽 0.69mm(下限 0.25mm 的 2.7 倍),扫得动。
|
|
8285
|
+
// 若沿用 h=35,条码 11.5mm 高 → 整格 ≈19.6mm,14 行 296mm 超一页。
|
|
8238
8286
|
sheet.innerHTML = items.map(x => bcMode === 'a4'
|
|
8239
|
-
? `<div class="bc-label"><div class="bc-nm" title="${esc(x.name)}">${esc(x.name)}</div>${code128Svg(x.code, { mw: 2, h:
|
|
8287
|
+
? `<div class="bc-label"><div class="bc-nm" title="${esc(x.name)}">${esc(x.name)}</div>${code128Svg(x.code, { mw: 2, h: 30 })}<div class="bc-no">${esc(x.code)}</div></div>`
|
|
8240
8288
|
: `<div class="bc-label"><div class="bc-hd"><span class="bc-no">${esc(x.code)}</span> <span class="bc-nm" title="${esc(x.name)}">${esc(x.name)}</span></div>${code128Svg(x.code, { mw: 2, h: bcMode === '30x10' ? 25 : 40, quiet: bcMode === '30x10' ? 2 : 10 })}</div>`
|
|
8241
8289
|
).join('');
|
|
8242
8290
|
applyBcPage();
|