|
|
@@ -184,13 +184,46 @@ select:focus,input:focus{border-color:var(--navy)}
|
|
|
padding:0 4px;border-radius:0 0 0 4px;font-weight:700}
|
|
|
.anchor{font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:var(--ink-faint);white-space:nowrap}
|
|
|
|
|
|
-/* 工具表格(复用 .steps 分区表头) */
|
|
|
-.tools-table td{min-width:88px}
|
|
|
-.tools-table td:first-child{min-width:120px}
|
|
|
-.tools-table .tcell-ul{padding-left:15px;margin:0}
|
|
|
-.tools-table .pill{margin:1px 3px 2px 0;display:inline-block}
|
|
|
-.case{border:1px dashed var(--line-dark);border-radius:6px;padding:6px 9px;margin:0 0 5px;font-size:11.5px;min-width:200px}
|
|
|
-.case .ck{color:var(--amber);font-weight:700;margin-right:6px}
|
|
|
+/* 工具表格(移植自 fixed_query_eval:案例逐行 rowspan + 限高展开) */
|
|
|
+.mw-ttwrap{overflow-x:auto;border:1px solid #e6ded2;border-radius:10px;box-shadow:0 2px 12px rgba(0,0,0,.05)}
|
|
|
+.mw-tt{border-collapse:separate;border-spacing:0;width:100%;min-width:1180px;background:#fff;font-size:12.5px}
|
|
|
+.mw-tt thead th{
|
|
|
+ position:sticky;top:0;z-index:2;text-align:left;white-space:nowrap;
|
|
|
+ background:linear-gradient(180deg,#2aa79b,#1c8076);color:#fff;font-weight:700;
|
|
|
+ padding:10px 12px;letter-spacing:.3px;border-right:1px solid rgba(255,255,255,.18);
|
|
|
+}
|
|
|
+.mw-tt thead th:last-child{border-right:none}
|
|
|
+.mw-tt thead tr:first-child th{top:0}
|
|
|
+.mw-tt thead tr:nth-child(2) th{top:38px}
|
|
|
+.mw-tt .th-group{text-align:center}
|
|
|
+.mw-tt .th-sub{background:linear-gradient(180deg,#36bdb0,#23897f);font-weight:600}
|
|
|
+.mw-tt tbody td{padding:9px 12px;vertical-align:top;line-height:1.6;
|
|
|
+ border-bottom:1px solid #f0eae0;border-right:1px solid #f5f0e8;color:#3a3a3a}
|
|
|
+.mw-tt tbody td:last-child{border-right:none}
|
|
|
+.mw-tt td.col-case{background:#fafdfc}
|
|
|
+.mw-tt tbody tr.tr-b td{background:#fbfaf6}
|
|
|
+.mw-tt tbody tr.tr-b td.col-case{background:#f6fbfa}
|
|
|
+.mw-tt tbody td.col-tool{font-weight:700;color:#176d64;white-space:nowrap;
|
|
|
+ border-left:3px solid #2aa79b;background:#f3faf8}
|
|
|
+.mw-tt tbody tr:hover td.col-tool{background:#e3f4f0}
|
|
|
+.mw-tt ul{margin:0;padding-left:17px}
|
|
|
+.mw-tt ul li{margin:3px 0}
|
|
|
+.mw-tt ul li::marker{color:#2aa79b}
|
|
|
+.mw-tt .layer-badge{display:inline-block;font-weight:700;font-size:11px;padding:2px 10px;border-radius:20px;white-space:nowrap}
|
|
|
+.mw-tt .layer-badge.make{color:#0e7490;background:#d6f0ee}
|
|
|
+.mw-tt .layer-badge.create{color:#b8731a;background:#fef0db}
|
|
|
+.mw-tt .dash{color:#c9c2b6}
|
|
|
+.mw-tt .tcell{position:relative;max-height:7.8em;overflow:hidden;transition:max-height .15s}
|
|
|
+.mw-tt .tcell.clamped{cursor:zoom-in}
|
|
|
+.mw-tt .tcell.clamped::after{content:'▾ 展开';position:absolute;left:0;right:0;bottom:0;
|
|
|
+ height:2.6em;display:flex;align-items:flex-end;justify-content:center;padding-bottom:2px;
|
|
|
+ font-size:11px;font-weight:700;color:#176d64;
|
|
|
+ background:linear-gradient(rgba(255,255,255,0),#fff 72%);pointer-events:none}
|
|
|
+.mw-tt tbody tr.tr-b .tcell.clamped::after{background:linear-gradient(rgba(251,250,246,0),#fbfaf6 72%)}
|
|
|
+.mw-tt td.col-case .tcell.clamped::after{background:linear-gradient(rgba(250,253,252,0),#fafdfc 72%)}
|
|
|
+.mw-tt tbody tr.tr-b td.col-case .tcell.clamped::after{background:linear-gradient(rgba(246,251,250,0),#f6fbfa 72%)}
|
|
|
+.mw-tt .tcell.open{max-height:none;cursor:zoom-out}
|
|
|
+.mw-tt .tcell.open::after{content:'';height:0}
|
|
|
|
|
|
/* 任务面板 */
|
|
|
#task-panel{
|
|
|
@@ -489,6 +522,7 @@ async function loadExtract(){
|
|
|
state.version = data.version;
|
|
|
syncVersionSelect();
|
|
|
body.innerHTML = isProc ? renderProcedures(data) : renderTools(data);
|
|
|
+ if (!isProc) requestAnimationFrame(markClampedCells);
|
|
|
}
|
|
|
function renderExtractHead(versions, data, missing){
|
|
|
const isProc = state.mode === 'process';
|
|
|
@@ -584,44 +618,84 @@ function ioCell(x, kind){
|
|
|
<td class="${cls}"><span class="anchor">${esc(x.anchor||'')}</span></td>`;
|
|
|
}
|
|
|
|
|
|
-/* ── 工具渲染(表格,与工序分区表头同语言)── */
|
|
|
+/* ── 工具渲染(表格,移植自 fixed_query_eval renderToolTable)── */
|
|
|
+const DASH = '<span class="dash">—</span>';
|
|
|
+function _toolCell(v){
|
|
|
+ if (v === null || v === undefined || v === '' || (Array.isArray(v) && !v.length)) return DASH;
|
|
|
+ if (Array.isArray(v)) return '<ul>' + v.map(x => `<li>${esc(String(x))}</li>`).join('') + '</ul>';
|
|
|
+ return esc(String(v));
|
|
|
+}
|
|
|
+function _scopeCell(v){
|
|
|
+ /* 作用域是 JSON 数组:按「、」拼接成短语,不出列表 */
|
|
|
+ if (v === null || v === undefined || (Array.isArray(v) && !v.length) || v === '') return DASH;
|
|
|
+ return esc(Array.isArray(v) ? v.join('、') : String(v));
|
|
|
+}
|
|
|
+function _ttCell(inner, clampable){
|
|
|
+ return clampable ? `<div class="tcell" onclick="this.classList.toggle('open')">${inner}</div>` : inner;
|
|
|
+}
|
|
|
+function _toolCellContent(c, t){
|
|
|
+ let inner, cls = '', clampable = true, style = '';
|
|
|
+ if (c === '工具名称'){
|
|
|
+ cls = 'col-tool'; clampable = false; inner = `🔧 ${esc(t[c] || '(未命名)')}`;
|
|
|
+ } else if (c === '来源链接'){
|
|
|
+ clampable = false;
|
|
|
+ inner = t[c] ? `<a href="${esc(t[c])}" target="_blank" style="color:#176d64;font-weight:600;">🔗 打开</a>` : DASH;
|
|
|
+ } else if (c === '创作层级'){
|
|
|
+ clampable = false;
|
|
|
+ inner = t[c] ? `<span class="layer-badge ${t[c] === '制作层' ? 'make' : 'create'}">${esc(t[c])}</span>` : DASH;
|
|
|
+ } else if (c === '实质作用域' || c === '形式作用域'){
|
|
|
+ inner = _scopeCell(t[c]);
|
|
|
+ } else {
|
|
|
+ inner = _toolCell(t[c]);
|
|
|
+ }
|
|
|
+ if (['输入','输出','用法','缺点'].includes(c)) style = 'max-width:240px;';
|
|
|
+ else if (c === '实质作用域' || c === '形式作用域') style = 'max-width:170px;';
|
|
|
+ else if (!clampable) style = 'white-space:nowrap;';
|
|
|
+ return {inner, cls, clampable, style};
|
|
|
+}
|
|
|
+function _td(c, t, rowspan){
|
|
|
+ const {inner, cls, clampable, style} = _toolCellContent(c, t);
|
|
|
+ const rs = rowspan > 1 ? ` rowspan="${rowspan}"` : '';
|
|
|
+ return `<td class="${cls}" style="${style}"${rs}>${_ttCell(inner, clampable)}</td>`;
|
|
|
+}
|
|
|
+function _caseTd(cse, key){
|
|
|
+ const v = (cse && cse[key] != null && cse[key] !== '') ? esc(String(cse[key])) : DASH;
|
|
|
+ return `<td class="col-case" style="max-width:210px;">${_ttCell(v, true)}</td>`;
|
|
|
+}
|
|
|
function renderTools(data){
|
|
|
const tools = data.tools || [];
|
|
|
if (!tools.length) return '<div class="empty">本版本无工具</div>';
|
|
|
- const pills = (a, cls) => (Array.isArray(a)?a:(a?[a]:[]))
|
|
|
- .flatMap(x=>String(x).split('、')).filter(Boolean)
|
|
|
- .map(x=>`<span class="pill ${cls}">${esc(x)}</span>`).join(' ');
|
|
|
- const ul = (a, style) => (a&&a.length) ? `<ul class="tcell-ul"${style?` style="${style}"`:''}>${a.map(x=>`<li>${esc(x)}</li>`).join('')}</ul>` : '';
|
|
|
- const rows = tools.map(t => {
|
|
|
- const cases = (t['案例']||[]).map(c=>`<div class="case">
|
|
|
- <div><span class="ck">入</span>${esc(c['输入']||'')}</div>
|
|
|
- <div><span class="ck">出</span>${esc(c['输出']||'')}</div>
|
|
|
- ${c['效果']?`<div><span class="ck">效</span>${esc(c['效果'])}</div>`:''}</div>`).join('');
|
|
|
- const name = `<b>${esc(t['工具名称']||'(未命名)')}</b>
|
|
|
- ${t['来源链接']?`<br><a class="anchor" href="${esc(t['来源链接'])}" target="_blank">来源 ↗</a>`:''}
|
|
|
- ${t['最新更新时间']?`<br><span class="anchor">更新 ${esc(t['最新更新时间'])}</span>`:''}`;
|
|
|
- return `<tr>
|
|
|
- <td>${name}</td>
|
|
|
- <td>${t['创作层级']?`<span class="pill navy">${esc(t['创作层级'])}</span>`:''}</td>
|
|
|
- <td>${pills(t['实质作用域'],'amber')}</td>
|
|
|
- <td>${pills(t['形式作用域'],'teal')}</td>
|
|
|
- <td class="c-in">${esc(t['输入']||'')}</td>
|
|
|
- <td class="c-out">${esc(t['输出']||'')}</td>
|
|
|
- <td>${ul(t['用法'])}</td>
|
|
|
- <td>${cases}</td>
|
|
|
- <td>${ul(t['缺点'],'color:var(--seal)')}</td>
|
|
|
- </tr>`;
|
|
|
+ /* 案例 group(输入/输出/效果)放在 用法 后、缺点 前;用 colspan/rowspan 做两层表头 */
|
|
|
+ const before = ['工具名称','创作层级','实质作用域','形式作用域','输入','输出','用法'];
|
|
|
+ const after = ['缺点','来源链接','最新更新时间'];
|
|
|
+ const thead = `<thead>
|
|
|
+ <tr>
|
|
|
+ ${before.map(c => `<th rowspan="2">${c}</th>`).join('')}
|
|
|
+ <th colspan="3" class="th-group">案例</th>
|
|
|
+ ${after.map(c => `<th rowspan="2">${c}</th>`).join('')}
|
|
|
+ </tr>
|
|
|
+ <tr>${['输入','输出','效果'].map(c => `<th class="th-sub">${c}</th>`).join('')}</tr>
|
|
|
+ </thead>`;
|
|
|
+ const rows = tools.map((t, ti) => {
|
|
|
+ const cases = (Array.isArray(t['案例']) && t['案例'].length) ? t['案例'] : [null];
|
|
|
+ const K = cases.length;
|
|
|
+ const par = ti % 2 ? 'tr-b' : 'tr-a';
|
|
|
+ return cases.map((cse, i) => {
|
|
|
+ const caseTds = `${_caseTd(cse,'输入')}${_caseTd(cse,'输出')}${_caseTd(cse,'效果')}`;
|
|
|
+ if (i === 0){
|
|
|
+ return `<tr class="${par}">${before.map(c => _td(c, t, K)).join('')}${caseTds}${after.map(c => _td(c, t, K)).join('')}</tr>`;
|
|
|
+ }
|
|
|
+ return `<tr class="${par}">${caseTds}</tr>`;
|
|
|
+ }).join('');
|
|
|
}).join('');
|
|
|
- return `<div style="overflow-x:auto"><table class="steps tools-table">
|
|
|
- <thead>
|
|
|
- <tr><th class="h-req" colspan="2">工 具</th><th class="h-in" colspan="2">作 用 域</th><th class="h-im" colspan="2">输 入 / 输 出</th><th class="h-out" colspan="3">详 情</th></tr>
|
|
|
- <tr>
|
|
|
- <th class="h-req2">名称</th><th class="h-req2">创作层级</th>
|
|
|
- <th class="h-in2">实质作用域</th><th class="h-in2">形式作用域</th>
|
|
|
- <th class="h-im2">输入</th><th class="h-im2">输出</th>
|
|
|
- <th class="h-out2">用法</th><th class="h-out2">案例</th><th class="h-out2">缺点</th>
|
|
|
- </tr>
|
|
|
- </thead><tbody>${rows}</tbody></table></div>`;
|
|
|
+ return `<div class="mw-ttwrap"><table class="mw-tt">${thead}<tbody>${rows}</tbody></table></div>`;
|
|
|
+}
|
|
|
+/* 渲染后标记真正溢出的单元格(才显示蒙版+可点击) */
|
|
|
+function markClampedCells(){
|
|
|
+ document.querySelectorAll('#xp-body .mw-tt .tcell').forEach(el => {
|
|
|
+ if (!el.classList.contains('open') && el.scrollHeight > el.clientHeight + 2) el.classList.add('clamped');
|
|
|
+ else if (el.scrollHeight <= el.clientHeight + 2) el.classList.remove('clamped');
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/* ════ 解构任务 ════ */
|