Files
mama-box/topic/_shared/wizard.js

145 lines
8.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 计算器分步模式(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);