计算器改分步模式(wizard.js,12 个含产假计算器模式)、测试卡图文分开、百宝箱火苗去圈、关注前3条头像各自2~6秒换(v26110419)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
144
topic/_shared/wizard.js
Normal file
144
topic/_shared/wizard.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/* 计算器分步模式(11-04 用户:「其他计算器都按生娃计算器那种模式:点进去一步一步填,最后出答案」)
|
||||
用法:页面原来的计算逻辑不动(照样实时往结果框里写),本脚本只管「显示哪一块」:
|
||||
CKW.init({ root: '.calc', res: '#res', after: '#scroll > section.calc ~ *' })
|
||||
· root 里每个 .fld 是一题;.tabs(算法切换)单独一题,题目取 data-q;包在 div 里的 .fld 会被拆开,带 .hide 的跳过;
|
||||
.calc 直属的 .hint 跟着上一题。
|
||||
· 必填:有 input/select/.seg 的题要先填;标签里写了「选填」或 .fld 带 data-opt 的不拦。
|
||||
· 只有一组 .seg / .tabs 的题,点完自动下一题(同生娃计算器)。
|
||||
· 最后一题按钮是「看结果」:结果框里还是 .empty(没填全 / 数不对)就不放行,把那句提示显示出来。
|
||||
· 结果屏:只出结果框 + after 选中的内容(详细说明等)+「重新算」(回第 1 题,填过的都留着)。
|
||||
· 记住停在哪一屏(AGENTS 规则:测试 / 计算器再进来回到最后停的屏),键 mmbox.wz.v1,按页面路径分。 */
|
||||
(function (g) {
|
||||
'use strict';
|
||||
var KEY = 'mmbox.wz.v1';
|
||||
function load() { try { return JSON.parse(localStorage.getItem(KEY) || '{}') || {}; } catch (e) { return {}; } }
|
||||
function save(v) { try { var m = load(); m[location.pathname] = v; localStorage.setItem(KEY, JSON.stringify(m)); } catch (e) {} }
|
||||
function hidden(el) { return el.classList.contains('hide') || el.hidden; }
|
||||
function txt(el) { return el ? el.textContent.replace(/\s+/g, ' ').trim() : ''; }
|
||||
|
||||
function init(o) {
|
||||
var root = typeof o.root === 'string' ? document.querySelector(o.root) : o.root;
|
||||
var res = typeof o.res === 'string' ? document.querySelector(o.res) : o.res;
|
||||
if (!root || !res || root.__wz) return;
|
||||
root.__wz = 1;
|
||||
var after = o.after ? [].slice.call(document.querySelectorAll(o.after)).filter(function (e) { return !/toast|mask/.test(e.className + ' ' + e.id); }) : [];
|
||||
var sc = document.getElementById('scroll') || document.scrollingElement;
|
||||
root.classList.add('wz');
|
||||
|
||||
/* 顶部进度 + 底部按钮 */
|
||||
var top = document.createElement('div'); top.className = 'wz-top';
|
||||
top.innerHTML = '<span class="wz-n"></span><span class="wz-dots"></span>';
|
||||
var bot = document.createElement('div'); bot.className = 'wz-bot';
|
||||
bot.innerHTML = '<p class="wz-err" role="alert"></p><div class="wz-act"><button type="button" class="wz-prev">‹ 上一步</button><button type="button" class="wz-next"></button></div><p class="wz-tip"></p>';
|
||||
var again = document.createElement('div'); again.className = 'wz-again';
|
||||
again.innerHTML = '<button type="button" class="wz-re"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 0 0-14.3-4.9L4 8M4 4v4h4M4 13a8 8 0 0 0 14.3 4.9L20 16M20 20v-4h-4"/></svg>重新算</button><small>填过的都还在,改哪项点哪项</small>';
|
||||
root.insertBefore(top, root.firstChild);
|
||||
root.appendChild(bot);
|
||||
res.parentNode.insertBefore(again, res.nextSibling);
|
||||
var nEl = top.querySelector('.wz-n'), dEl = top.querySelector('.wz-dots'), err = bot.querySelector('.wz-err'),
|
||||
prev = bot.querySelector('.wz-prev'), next = bot.querySelector('.wz-next'), tip = bot.querySelector('.wz-tip');
|
||||
|
||||
/* 题目单元:每次现算(页面切 tabs 会显示 / 隐藏不同的 .fld) */
|
||||
function units() {
|
||||
var out = [];
|
||||
[].forEach.call(root.children, function (c) {
|
||||
if (c === top || c === bot || c === res || c === again || hidden(c)) return;
|
||||
if (c.classList.contains('fld') || c.classList.contains('tabs')) out.push([c]);
|
||||
else if (c.querySelector('.fld')) [].forEach.call(c.querySelectorAll('.fld'), function (f) { if (!hidden(f)) out.push([f]); });
|
||||
else if (out.length) out[out.length - 1].push(c); /* 跟在题后的 .hint / 「再加一个孩子」等 */
|
||||
else out.push([c]);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
/* 能被藏的块:root 的直属子元素(除进度 / 按钮 / 结果框)+ 壳里的 .fld;题里面的东西不碰 */
|
||||
function all() {
|
||||
var out = [];
|
||||
[].forEach.call(root.children, function (c) {
|
||||
if (c === top || c === bot || c === res || c === again) return;
|
||||
out.push(c);
|
||||
if (!c.classList.contains('fld') && !c.classList.contains('tabs')) [].push.apply(out, c.querySelectorAll('.fld'));
|
||||
});
|
||||
return out;
|
||||
}
|
||||
function single(u) { /* 只有一组按钮可选 → 选完自动下一题 */
|
||||
var el = u[0];
|
||||
if (el.classList.contains('tabs')) return true;
|
||||
return el.querySelectorAll('.seg').length === 1 && !el.querySelector('input,select,.stp');
|
||||
}
|
||||
function missing(u) {
|
||||
var el = u[0];
|
||||
if (el.hasAttribute('data-opt') || /选填/.test(txt(el.querySelector('.lb')))) return '';
|
||||
var bad = '';
|
||||
[].forEach.call(el.querySelectorAll('input,select'), function (i) {
|
||||
if (bad || i.type === 'hidden' || i.disabled) return;
|
||||
var lb = i.closest('div') && i.closest('div').querySelector('.lb');
|
||||
if (lb && /选填/.test(txt(lb))) return;
|
||||
if (!String(i.value).trim()) bad = '先填一下「' + (txt(lb || el.querySelector('.lb')).replace(/选填.*$/, '') || '这一项') + '」';
|
||||
});
|
||||
if (!bad) [].forEach.call(el.querySelectorAll('.seg'), function (s) { if (!bad && !s.querySelector('.on')) bad = '先选一下' + (txt(el.querySelector('.lb')) ? '「' + txt(el.querySelector('.lb')).replace(/\s.*$/, '') + '」' : ''); });
|
||||
return bad;
|
||||
}
|
||||
|
||||
var i = 0, mode = 'q';
|
||||
function show() {
|
||||
var us = units();
|
||||
if (i >= us.length) i = us.length - 1;
|
||||
if (i < 0) i = 0;
|
||||
all().forEach(function (e) { e.classList.add('wz-off'); });
|
||||
[].forEach.call(root.querySelectorAll('.wz-in'), function (e) { e.classList.remove('wz-in'); });
|
||||
root.classList.toggle('wz-res', mode === 'r');
|
||||
document.documentElement.classList.toggle('wz-q', mode !== 'r'); /* 答题时页面底部后加的相关推荐 / 评论区也先不出 */
|
||||
after.forEach(function (e) { e.classList.toggle('wz-hold', mode !== 'r'); });
|
||||
err.textContent = '';
|
||||
if (mode === 'r') {
|
||||
res.classList.remove('wz-pop'); void res.offsetWidth; res.classList.add('wz-pop');
|
||||
save('r'); sc.scrollTop = 0; return;
|
||||
}
|
||||
var u = us[i];
|
||||
u.forEach(function (e) {
|
||||
e.classList.remove('wz-off'); e.classList.add('wz-in');
|
||||
if (e.parentNode !== root) e.parentNode.classList.remove('wz-off'); /* 包在 #fLmp 这类壳里的题 */
|
||||
});
|
||||
var q = u[0].classList.contains('tabs') ? (u[0].getAttribute('data-q') || '先选一种算法') : '';
|
||||
u[0].setAttribute('data-wzq', q);
|
||||
nEl.textContent = '第 ' + (i + 1) + ' / ' + us.length + ' 步';
|
||||
dEl.innerHTML = us.map(function (_, k) { return '<i' + (k < i ? ' class="d"' : k === i ? ' class="c"' : '') + '></i>'; }).join('');
|
||||
prev.hidden = !i;
|
||||
var last = i === us.length - 1;
|
||||
next.textContent = last ? '看结果 ›' : '下一步 ›';
|
||||
next.classList.toggle('go', last);
|
||||
tip.textContent = single(u) && !last ? '选完自动下一步' : '';
|
||||
save(i); sc.scrollTop = 0;
|
||||
}
|
||||
function go(d) {
|
||||
var us = units();
|
||||
if (d > 0) {
|
||||
var m = missing(us[i]);
|
||||
if (m) { err.textContent = m; root.classList.remove('wz-shake'); void root.offsetWidth; root.classList.add('wz-shake'); return; }
|
||||
if (i === us.length - 1) {
|
||||
var e = res.querySelector('.empty');
|
||||
if (e) { err.textContent = txt(e); return; }
|
||||
mode = 'r'; return show();
|
||||
}
|
||||
}
|
||||
i += d; show();
|
||||
}
|
||||
prev.addEventListener('click', function () { go(-1); });
|
||||
next.addEventListener('click', function () { go(1); });
|
||||
again.querySelector('.wz-re').addEventListener('click', function () { mode = 'q'; i = 0; show(); });
|
||||
/* 单选题点完自动下一步(等页面自己的点击处理先跑完) */
|
||||
root.addEventListener('click', function (e) {
|
||||
if (mode !== 'q') return;
|
||||
var b = e.target.closest('.seg button,.tabs button'); if (!b) return;
|
||||
var us = units(), u = us[i];
|
||||
if (!u || u.indexOf(b.closest('.fld,.tabs')) < 0 || !single(u) || i === us.length - 1) return;
|
||||
setTimeout(function () { if (mode === 'q' && units()[i] && units()[i][0] === u[0]) go(1); }, 320);
|
||||
});
|
||||
|
||||
var sv = load()[location.pathname];
|
||||
if (sv === 'r' && !res.querySelector('.empty')) mode = 'r';
|
||||
else if (typeof sv === 'number') i = sv;
|
||||
show();
|
||||
}
|
||||
g.CKW = { init: init };
|
||||
})(window);
|
||||
Reference in New Issue
Block a user