|
@@ -1382,7 +1382,6 @@
|
|
|
<div class="navrow" id="navQRow" style="margin-bottom:4px;">
|
|
<div class="navrow" id="navQRow" style="margin-bottom:4px;">
|
|
|
<span class="navlab">Query</span>
|
|
<span class="navlab">Query</span>
|
|
|
<div id="navQ" style="display:flex;gap:8px;flex-wrap:wrap;flex:1"></div>
|
|
<div id="navQ" style="display:flex;gap:8px;flex-wrap:wrap;flex:1"></div>
|
|
|
- <button id="toolBatchBtn" onclick="openToolBatchModal()" style="margin-right:8px">🔧 工具解构</button>
|
|
|
|
|
<button id="refresh2" onclick="loadData(true)">↻ 刷新 runs</button>
|
|
<button id="refresh2" onclick="loadData(true)">↻ 刷新 runs</button>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="navrow" id="mxRow" style="margin-bottom:4px;">
|
|
<div class="navrow" id="mxRow" style="margin-bottom:4px;">
|
|
@@ -1488,12 +1487,70 @@
|
|
|
|
|
|
|
|
<!-- fixed_query_eval:图片放大灯箱(用 dialog 才能叠在详情 modal 之上)-->
|
|
<!-- fixed_query_eval:图片放大灯箱(用 dialog 才能叠在详情 modal 之上)-->
|
|
|
<style>
|
|
<style>
|
|
|
- #imgLightbox { border:none; background:transparent; padding:0; max-width:100vw; max-height:100vh; overflow:visible; }
|
|
|
|
|
- #imgLightbox::backdrop { background: rgba(0,0,0,.85); }
|
|
|
|
|
- #imgLightbox img { max-width:92vw; max-height:92vh; object-fit:contain; border-radius:8px; cursor:zoom-out; box-shadow:0 10px 40px rgba(0,0,0,.5); display:block; }
|
|
|
|
|
|
|
+ #imgLightbox { border:none; background:transparent; padding:0; width:100vw; height:100vh; max-width:100vw; max-height:100vh; overflow:hidden; }
|
|
|
|
|
+ #imgLightbox::backdrop { background: rgba(0,0,0,.88); }
|
|
|
|
|
+ #imgLightbox .lb-stage { width:100%; height:100%; display:flex; align-items:center; justify-content:center; position:relative; }
|
|
|
|
|
+ #imgLightbox .lb-stage img { max-width:86vw; max-height:90vh; object-fit:contain; border-radius:8px; box-shadow:0 10px 40px rgba(0,0,0,.5); cursor:pointer; display:block; }
|
|
|
|
|
+ #imgLightbox .lb-nav { position:absolute; top:50%; transform:translateY(-50%); width:50px; height:68px; border:none; border-radius:10px; background:rgba(255,255,255,.16); color:#fff; font-size:36px; line-height:1; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:background .15s; user-select:none; }
|
|
|
|
|
+ #imgLightbox .lb-nav:hover { background:rgba(255,255,255,.32); }
|
|
|
|
|
+ #imgLightbox .lb-prev { left:26px; } #imgLightbox .lb-next { right:26px; }
|
|
|
|
|
+ #imgLightbox .lb-nav.hidden { display:none; }
|
|
|
|
|
+ #imgLightbox .lb-counter { position:absolute; bottom:22px; left:50%; transform:translateX(-50%); color:#fff; font-size:13px; font-weight:600; background:rgba(0,0,0,.55); padding:5px 14px; border-radius:20px; }
|
|
|
|
|
+ #imgLightbox .lb-close { position:absolute; top:22px; right:26px; width:38px; height:38px; border:none; border-radius:50%; background:rgba(255,255,255,.16); color:#fff; font-size:18px; cursor:pointer; }
|
|
|
|
|
+ #imgLightbox .lb-close:hover { background:rgba(255,255,255,.32); }
|
|
|
|
|
+
|
|
|
|
|
+ /* fixed_query_eval:工具解构表格美化 + 单元格限高展开 */
|
|
|
|
|
+ .fqe-ttwrap { overflow-x:auto; border:1px solid #e6ded2; border-radius:10px; box-shadow:0 2px 12px rgba(0,0,0,.05); }
|
|
|
|
|
+ .fqe-tt { border-collapse:separate; border-spacing:0; width:100%; min-width:1180px; background:#fff; font-size:12.5px; }
|
|
|
|
|
+ .fqe-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);
|
|
|
|
|
+ }
|
|
|
|
|
+ .fqe-tt thead th:last-child { border-right:none; }
|
|
|
|
|
+ .fqe-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; }
|
|
|
|
|
+ .fqe-tt tbody td:last-child { border-right:none; }
|
|
|
|
|
+ /* 两层表头:第二行子表头下移吸顶;group/sub 表头样式 */
|
|
|
|
|
+ .fqe-tt2 thead tr:first-child th { top:0; }
|
|
|
|
|
+ .fqe-tt2 thead tr:nth-child(2) th { top:38px; }
|
|
|
|
|
+ .fqe-tt .th-group { text-align:center; }
|
|
|
|
|
+ .fqe-tt .th-sub { background:linear-gradient(180deg,#36bdb0,#23897f); font-weight:600; }
|
|
|
|
|
+ .fqe-tt td.col-case { background:#fafdfc; }
|
|
|
|
|
+ /* 斑马纹按「工具」分组(rowspan 安全),不用 nth-child */
|
|
|
|
|
+ .fqe-tt tbody tr.tr-b td { background:#fbfaf6; }
|
|
|
|
|
+ .fqe-tt tbody tr.tr-b td.col-case { background:#f6fbfa; }
|
|
|
|
|
+ .fqe-tt tbody td.col-tool { font-weight:700; color:#176d64; white-space:nowrap;
|
|
|
|
|
+ border-left:3px solid #2aa79b; background:#f3faf8; }
|
|
|
|
|
+ .fqe-tt tbody tr:hover td.col-tool { background:#e3f4f0; }
|
|
|
|
|
+ .fqe-tt ul { margin:0; padding-left:17px; }
|
|
|
|
|
+ .fqe-tt ul li { margin:3px 0; }
|
|
|
|
|
+ .fqe-tt ul li::marker { color:#2aa79b; }
|
|
|
|
|
+ .fqe-tt .layer-badge { display:inline-block; font-weight:700; font-size:11px; padding:2px 10px; border-radius:20px; white-space:nowrap; }
|
|
|
|
|
+ .fqe-tt .layer-badge.make { color:#0e7490; background:#d6f0ee; }
|
|
|
|
|
+ .fqe-tt .layer-badge.create { color:#b8731a; background:#fef0db; }
|
|
|
|
|
+ .fqe-tt .dash { color:#c9c2b6; }
|
|
|
|
|
+ /* 单元格限高 + 渐变蒙版 + 点击展开 */
|
|
|
|
|
+ .fqe-tt .tcell { position:relative; max-height:7.8em; overflow:hidden; transition:max-height .15s; }
|
|
|
|
|
+ .fqe-tt .tcell.clamped { cursor:zoom-in; }
|
|
|
|
|
+ .fqe-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; }
|
|
|
|
|
+ .fqe-tt tbody tr.tr-b .tcell.clamped::after { background:linear-gradient(rgba(251,250,246,0), #fbfaf6 72%); }
|
|
|
|
|
+ .fqe-tt td.col-case .tcell.clamped::after { background:linear-gradient(rgba(250,253,252,0), #fafdfc 72%); }
|
|
|
|
|
+ .fqe-tt tbody tr.tr-b td.col-case .tcell.clamped::after { background:linear-gradient(rgba(246,251,250,0), #f6fbfa 72%); }
|
|
|
|
|
+ .fqe-tt .tcell.open { max-height:none; cursor:zoom-out; }
|
|
|
|
|
+ .fqe-tt .tcell.open::after { content:''; height:0; }
|
|
|
</style>
|
|
</style>
|
|
|
- <dialog id="imgLightbox" onclick="this.close()">
|
|
|
|
|
- <img id="imgLightboxImg" referrerpolicy="no-referrer" alt="">
|
|
|
|
|
|
|
+ <dialog id="imgLightbox">
|
|
|
|
|
+ <div class="lb-stage" onclick="if(event.target===this) imgLightbox.close()">
|
|
|
|
|
+ <button class="lb-nav lb-prev" onclick="lightboxNav(-1)" aria-label="上一张">‹</button>
|
|
|
|
|
+ <img id="imgLightboxImg" referrerpolicy="no-referrer" alt="" onclick="lightboxNav(1)" title="点击看下一张">
|
|
|
|
|
+ <button class="lb-nav lb-next" onclick="lightboxNav(1)" aria-label="下一张">›</button>
|
|
|
|
|
+ <div class="lb-counter" id="lbCounter"></div>
|
|
|
|
|
+ <button class="lb-close" onclick="imgLightbox.close()" aria-label="关闭">✕</button>
|
|
|
|
|
+ </div>
|
|
|
</dialog>
|
|
</dialog>
|
|
|
|
|
|
|
|
<!-- fixed_query_eval:批量工具解构 · 选帖弹层 -->
|
|
<!-- fixed_query_eval:批量工具解构 · 选帖弹层 -->
|
|
@@ -2423,7 +2480,9 @@
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const filteredResults = f.results.filter(resultsFilter);
|
|
const filteredResults = f.results.filter(resultsFilter);
|
|
|
- const chans = ["all", ...f.platforms];
|
|
|
|
|
|
|
+ // 渠道从实际帖子的 platformKey 推导(不依赖 f.platforms,DB 回退时它为空),按 PLATC 顺序
|
|
|
|
|
+ const present = new Set(filteredResults.map(r => r.platformKey).filter(Boolean));
|
|
|
|
|
+ const chans = ["all", ...Object.keys(PLATC).filter(k => present.has(k))];
|
|
|
document.getElementById("navC").innerHTML = chans.map(c => {
|
|
document.getElementById("navC").innerHTML = chans.map(c => {
|
|
|
const n = c === "all" ? filteredResults.length : filteredResults.filter(r => r.platformKey === c).length;
|
|
const n = c === "all" ? filteredResults.length : filteredResults.filter(r => r.platformKey === c).length;
|
|
|
return `<span class="tab ${c === st.channel ? 'on' : ''}" data-c="${c}">${c === "all" ? "全部" : (PLATC[c] || c)} <small>${n}</small></span>`;
|
|
return `<span class="tab ${c === st.channel ? 'on' : ''}" data-c="${c}">${c === "all" ? "全部" : (PLATC[c] || c)} <small>${n}</small></span>`;
|
|
@@ -2853,12 +2912,36 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // fixed_query_eval:图片点击放大(灯箱用 dialog,叠在详情 modal 上层)
|
|
|
|
|
|
|
+ // fixed_query_eval:图片画廊灯箱(dialog 叠在详情 modal 上层,支持 ←→ 切换)
|
|
|
|
|
+ let _lbImgs = [], _lbIdx = 0;
|
|
|
function openLightbox(src) {
|
|
function openLightbox(src) {
|
|
|
- const d = document.getElementById('imgLightbox');
|
|
|
|
|
- document.getElementById('imgLightboxImg').src = src;
|
|
|
|
|
- d.showModal();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const it = _curDetailItem();
|
|
|
|
|
+ _lbImgs = (it && Array.isArray(it.images) && it.images.length) ? it.images.slice() : [src];
|
|
|
|
|
+ _lbIdx = Math.max(0, _lbImgs.indexOf(src));
|
|
|
|
|
+ _lbRender();
|
|
|
|
|
+ document.getElementById('imgLightbox').showModal();
|
|
|
|
|
+ }
|
|
|
|
|
+ function _lbRender() {
|
|
|
|
|
+ if (!_lbImgs.length) return;
|
|
|
|
|
+ document.getElementById('imgLightboxImg').src = _lbImgs[_lbIdx];
|
|
|
|
|
+ const multi = _lbImgs.length > 1;
|
|
|
|
|
+ document.querySelectorAll('#imgLightbox .lb-nav').forEach(b => b.classList.toggle('hidden', !multi));
|
|
|
|
|
+ const ctr = document.getElementById('lbCounter');
|
|
|
|
|
+ ctr.textContent = `${_lbIdx + 1} / ${_lbImgs.length}`;
|
|
|
|
|
+ ctr.style.display = multi ? '' : 'none';
|
|
|
|
|
+ }
|
|
|
|
|
+ function lightboxNav(d) {
|
|
|
|
|
+ if (_lbImgs.length < 2) return;
|
|
|
|
|
+ _lbIdx = (_lbIdx + d + _lbImgs.length) % _lbImgs.length; // 循环切换
|
|
|
|
|
+ _lbRender();
|
|
|
|
|
+ }
|
|
|
|
|
+ // 键盘 ←/→ 切换(ESC 关闭走 dialog 默认)
|
|
|
|
|
+ document.addEventListener('keydown', e => {
|
|
|
|
|
+ const lb = document.getElementById('imgLightbox');
|
|
|
|
|
+ if (!lb || !lb.open) return;
|
|
|
|
|
+ if (e.key === 'ArrowLeft') { e.preventDefault(); lightboxNav(-1); }
|
|
|
|
|
+ else if (e.key === 'ArrowRight') { e.preventDefault(); lightboxNav(1); }
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
// ════════ fixed_query_eval:工具解构(批量选帖 + 单帖 + 结果渲染)════════
|
|
// ════════ fixed_query_eval:工具解构(批量选帖 + 单帖 + 结果渲染)════════
|
|
|
const TOOL_MODEL_LABEL = 'gemini-3.1-flash-lite';
|
|
const TOOL_MODEL_LABEL = 'gemini-3.1-flash-lite';
|
|
@@ -2919,14 +3002,17 @@
|
|
|
// ── 详情页「工具解构」tab ──
|
|
// ── 详情页「工具解构」tab ──
|
|
|
function _curDetailItem() { return VIEW[detailDialog.dataset.activeIdx]; }
|
|
function _curDetailItem() { return VIEW[detailDialog.dataset.activeIdx]; }
|
|
|
|
|
|
|
|
|
|
+ let toolVersion = null; // null = 最新版本;切到历史版本时设为具体版本号
|
|
|
function loadToolsForCurrent() {
|
|
function loadToolsForCurrent() {
|
|
|
const it = _curDetailItem(); if (!it) return;
|
|
const it = _curDetailItem(); if (!it) return;
|
|
|
const pane = document.getElementById('modalContentTools');
|
|
const pane = document.getElementById('modalContentTools');
|
|
|
pane.innerHTML = '<div style="padding:40px;text-align:center;color:var(--muted)">加载中…</div>';
|
|
pane.innerHTML = '<div style="padding:40px;text-align:center;color:var(--muted)">加载中…</div>';
|
|
|
- fetch(`/api/tools_data?q=${encodeURIComponent(it.run)}&case_id=${encodeURIComponent(it.case_id)}`)
|
|
|
|
|
- .then(r => r.json()).then(d => { pane.innerHTML = d.exists ? renderToolCards(d) : toolSetupHTML(); })
|
|
|
|
|
|
|
+ const vparam = toolVersion ? `&version=${encodeURIComponent(toolVersion)}` : '';
|
|
|
|
|
+ fetch(`/api/tools_data?q=${encodeURIComponent(it.run)}&case_id=${encodeURIComponent(it.case_id)}${vparam}`)
|
|
|
|
|
+ .then(r => r.json()).then(d => { pane.innerHTML = d.exists ? renderToolCards(d) : toolSetupHTML(); requestAnimationFrame(_markClampedCells); })
|
|
|
.catch(() => { pane.innerHTML = '<div style="padding:40px;color:#c0392b">加载失败</div>'; });
|
|
.catch(() => { pane.innerHTML = '<div style="padding:40px;color:#c0392b">加载失败</div>'; });
|
|
|
}
|
|
}
|
|
|
|
|
+ function changeToolVersion(v) { toolVersion = v || null; loadToolsForCurrent(); }
|
|
|
function toolSetupHTML() {
|
|
function toolSetupHTML() {
|
|
|
return `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:48px 20px;">
|
|
return `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:48px 20px;">
|
|
|
<div style="font-size:36px;margin-bottom:14px;">🔧</div>
|
|
<div style="font-size:36px;margin-bottom:14px;">🔧</div>
|
|
@@ -2949,6 +3035,7 @@
|
|
|
}
|
|
}
|
|
|
function startToolExtractSingle() {
|
|
function startToolExtractSingle() {
|
|
|
const it = _curDetailItem(); if (!it) return;
|
|
const it = _curDetailItem(); if (!it) return;
|
|
|
|
|
+ toolVersion = null; // 新解构 → 完成后看最新版本
|
|
|
const pane = document.getElementById('modalContentTools');
|
|
const pane = document.getElementById('modalContentTools');
|
|
|
pane.innerHTML = `<div style="padding:48px;text-align:center;color:var(--muted)">⏳ 正在解构本帖工具…(${TOOL_MODEL_LABEL})</div>`;
|
|
pane.innerHTML = `<div style="padding:48px;text-align:center;color:var(--muted)">⏳ 正在解构本帖工具…(${TOOL_MODEL_LABEL})</div>`;
|
|
|
fetch('/api/extract_tools', { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
fetch('/api/extract_tools', { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
@@ -2960,7 +3047,8 @@
|
|
|
}
|
|
}
|
|
|
function reExtractTools() {
|
|
function reExtractTools() {
|
|
|
const it = _curDetailItem(); if (!it) return;
|
|
const it = _curDetailItem(); if (!it) return;
|
|
|
- if (!confirm('重新解构会覆盖已有结果,确定?')) return;
|
|
|
|
|
|
|
+ if (!confirm('重新解构会生成一个新版本(旧版本保留,可在版本下拉里切回),确定?')) return;
|
|
|
|
|
+ toolVersion = null; // 解构后看最新版本
|
|
|
const pane = document.getElementById('modalContentTools');
|
|
const pane = document.getElementById('modalContentTools');
|
|
|
pane.innerHTML = `<div style="padding:48px;text-align:center;color:var(--muted)">⏳ 重新解构中…(${TOOL_MODEL_LABEL})</div>`;
|
|
pane.innerHTML = `<div style="padding:48px;text-align:center;color:var(--muted)">⏳ 重新解构中…(${TOOL_MODEL_LABEL})</div>`;
|
|
|
fetch('/api/extract_tools', { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
fetch('/api/extract_tools', { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
@@ -2974,16 +3062,37 @@
|
|
|
: `<span>${esc(String(val))}</span>`;
|
|
: `<span>${esc(String(val))}</span>`;
|
|
|
return `<div style="margin-top:8px;font-size:13px;line-height:1.5;"><span style="font-size:12px;font-weight:700;color:${color || 'var(--muted)'};">${label}</span> ${body}</div>`;
|
|
return `<div style="margin-top:8px;font-size:13px;line-height:1.5;"><span style="font-size:12px;font-weight:700;color:${color || 'var(--muted)'};">${label}</span> ${body}</div>`;
|
|
|
}
|
|
}
|
|
|
- let toolViewMode = 'cards'; // 'cards' | 'table'
|
|
|
|
|
|
|
+ // 案例:新结构是 [{输入, 输出, 效果}] 对象数组(旧的可能是字符串,兼容)
|
|
|
|
|
+ function _renderCaseList(cases) {
|
|
|
|
|
+ return (cases || []).map(c => {
|
|
|
|
|
+ if (c === null || typeof c !== 'object') return `<li style="margin:2px 0;">${esc(String(c))}</li>`;
|
|
|
|
|
+ const parts = [];
|
|
|
|
|
+ if (c['输入']) parts.push(`<b>输入</b>:${esc(c['输入'])}`);
|
|
|
|
|
+ if (c['输出']) parts.push(`<b>输出</b>:${esc(c['输出'])}`);
|
|
|
|
|
+ if (c['效果']) parts.push(`<b>效果</b>:${esc(c['效果'])}`);
|
|
|
|
|
+ return `<li style="margin:3px 0;">${parts.join(';') || esc(JSON.stringify(c))}</li>`;
|
|
|
|
|
+ }).join('');
|
|
|
|
|
+ }
|
|
|
|
|
+ function _renderCaseBlock(cases) { // 卡片视图用
|
|
|
|
|
+ if (!Array.isArray(cases) || !cases.length) return '';
|
|
|
|
|
+ return `<div style="margin-top:8px;font-size:13px;line-height:1.5;"><span style="font-size:12px;font-weight:700;color:var(--muted);">案例</span><ul style="margin:2px 0 0;padding-left:18px;">${_renderCaseList(cases)}</ul></div>`;
|
|
|
|
|
+ }
|
|
|
|
|
+ let toolViewMode = 'table'; // 'cards' | 'table'(默认表格)
|
|
|
let _lastToolsData = null;
|
|
let _lastToolsData = null;
|
|
|
|
|
|
|
|
function renderToolCards(d) {
|
|
function renderToolCards(d) {
|
|
|
_lastToolsData = d;
|
|
_lastToolsData = d;
|
|
|
const tools = d.tools || [];
|
|
const tools = d.tools || [];
|
|
|
const toggleLabel = toolViewMode === 'table' ? '▤ 卡片视图' : '⊞ 表格视图';
|
|
const toggleLabel = toolViewMode === 'table' ? '▤ 卡片视图' : '⊞ 表格视图';
|
|
|
- const header = `<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
|
|
|
|
|
+ const versions = d.versions || [];
|
|
|
|
|
+ const curVer = d.version || versions[0] || '';
|
|
|
|
|
+ const verSel = versions.length
|
|
|
|
|
+ ? `<select onchange="changeToolVersion(this.value)" title="切换历史版本" style="font-size:12px;padding:3px 8px;border:1px solid var(--line);border-radius:6px;background:#fff;cursor:pointer;">${versions.map(v => `<option value="${esc(v)}" ${v === curVer ? 'selected' : ''}>${esc(v)}${v === versions[0] ? '(最新)' : ''}</option>`).join('')}</select>`
|
|
|
|
|
+ : (curVer ? `<span style="font-size:12px;color:var(--muted);">版本 ${esc(curVer)}</span>` : '');
|
|
|
|
|
+ const header = `<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;gap:10px;">
|
|
|
<div style="font-size:14px;color:var(--muted);">共 <b style="color:var(--ink);">${tools.length}</b> 个工具 · 模型 ${esc(d.model || TOOL_MODEL_LABEL)}</div>
|
|
<div style="font-size:14px;color:var(--muted);">共 <b style="color:var(--ink);">${tools.length}</b> 个工具 · 模型 ${esc(d.model || TOOL_MODEL_LABEL)}</div>
|
|
|
- <div style="display:flex;gap:8px;">
|
|
|
|
|
|
|
+ <div style="display:flex;gap:8px;align-items:center;flex-shrink:0;">
|
|
|
|
|
+ ${verSel}
|
|
|
<button onclick="toggleToolView()" style="font-size:12px;">${toggleLabel}</button>
|
|
<button onclick="toggleToolView()" style="font-size:12px;">${toggleLabel}</button>
|
|
|
<button onclick="reExtractTools()" style="font-size:12px;">↻ 重新解构</button>
|
|
<button onclick="reExtractTools()" style="font-size:12px;">↻ 重新解构</button>
|
|
|
</div>
|
|
</div>
|
|
@@ -2994,7 +3103,10 @@
|
|
|
|
|
|
|
|
function toggleToolView() {
|
|
function toggleToolView() {
|
|
|
toolViewMode = toolViewMode === 'table' ? 'cards' : 'table';
|
|
toolViewMode = toolViewMode === 'table' ? 'cards' : 'table';
|
|
|
- if (_lastToolsData) document.getElementById('modalContentTools').innerHTML = renderToolCards(_lastToolsData);
|
|
|
|
|
|
|
+ if (_lastToolsData) {
|
|
|
|
|
+ document.getElementById('modalContentTools').innerHTML = renderToolCards(_lastToolsData);
|
|
|
|
|
+ requestAnimationFrame(_markClampedCells);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function renderToolCardsBody(tools) {
|
|
function renderToolCardsBody(tools) {
|
|
@@ -3015,7 +3127,7 @@
|
|
|
${(tags.length || updated) ? `<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:8px;">${tags.join('')} ${updated}</div>` : ''}
|
|
${(tags.length || updated) ? `<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:8px;">${tags.join('')} ${updated}</div>` : ''}
|
|
|
<div style="display:flex;gap:28px;flex-wrap:wrap;">${_toolField('输入', t['输入'])}${_toolField('输出', t['输出'])}</div>
|
|
<div style="display:flex;gap:28px;flex-wrap:wrap;">${_toolField('输入', t['输入'])}${_toolField('输出', t['输出'])}</div>
|
|
|
${_toolField('用法', t['用法'])}
|
|
${_toolField('用法', t['用法'])}
|
|
|
- ${_toolField('案例', t['案例'])}
|
|
|
|
|
|
|
+ ${_renderCaseBlock(t['案例'])}
|
|
|
${_toolField('缺点', t['缺点'], '#b8651a')}
|
|
${_toolField('缺点', t['缺点'], '#b8651a')}
|
|
|
${link}
|
|
${link}
|
|
|
</div>`;
|
|
</div>`;
|
|
@@ -3028,38 +3140,77 @@
|
|
|
if (Array.isArray(v)) return '<ul style="margin:0;padding-left:15px;">' + v.map(x => `<li style="margin:1px 0;">${esc(String(x))}</li>`).join('') + '</ul>';
|
|
if (Array.isArray(v)) return '<ul style="margin:0;padding-left:15px;">' + v.map(x => `<li style="margin:1px 0;">${esc(String(x))}</li>`).join('') + '</ul>';
|
|
|
return esc(String(v));
|
|
return esc(String(v));
|
|
|
}
|
|
}
|
|
|
|
|
+ const DASH = '<span class="dash">—</span>';
|
|
|
|
|
+ // 单元格内容包一层 .tcell(可限高+点击展开);不可点的列(链接/徽章/工具名)不包
|
|
|
|
|
+ function _ttCell(inner, clampable) {
|
|
|
|
|
+ return clampable ? `<div class="tcell" onclick="this.classList.toggle('open')">${inner}</div>` : inner;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 普通列(案例 group 之外)的单元格内容
|
|
|
|
|
+ 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 {
|
|
|
|
|
+ const v = _toolCell(t[c]);
|
|
|
|
|
+ inner = (v === '<span style="color:#c4c4c4">—</span>') ? DASH : v;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (['输入', '输出', '用法', '缺点'].includes(c)) style = 'max-width:240px;';
|
|
|
|
|
+ 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 renderToolTable(tools) {
|
|
function renderToolTable(tools) {
|
|
|
- const cols = ['工具名称', '创作层级', '实质作用域', '形式作用域', '输入', '输出', '用法', '案例', '缺点', '来源链接', '最新更新时间'];
|
|
|
|
|
- const tdBase = 'border:1px solid var(--line);padding:7px 9px;vertical-align:top;font-size:12px;line-height:1.5;';
|
|
|
|
|
- const th = cols.map(c =>
|
|
|
|
|
- `<th style="${tdBase}background:#faf7f1;font-weight:700;white-space:nowrap;position:sticky;top:0;z-index:1;">${c}</th>`
|
|
|
|
|
- ).join('');
|
|
|
|
|
- const rows = tools.map(t => {
|
|
|
|
|
- const tds = cols.map(c => {
|
|
|
|
|
- let inner;
|
|
|
|
|
- if (c === '来源链接') {
|
|
|
|
|
- inner = t[c] ? `<a href="${esc(t[c])}" target="_blank" style="color:#2563eb;">🔗 打开</a>` : '<span style="color:#c4c4c4">—</span>';
|
|
|
|
|
- } else if (c === '创作层级' && t[c]) {
|
|
|
|
|
- const lc = t[c] === '制作层' ? '#0e7490' : '#b87918';
|
|
|
|
|
- const lbg = t[c] === '制作层' ? '#e0f2f1' : '#fef3e2';
|
|
|
|
|
- inner = `<span style="font-weight:700;color:${lc};background:${lbg};padding:1px 8px;border-radius:12px;white-space:nowrap;">${esc(t[c])}</span>`;
|
|
|
|
|
- } else {
|
|
|
|
|
- inner = _toolCell(t[c]);
|
|
|
|
|
|
|
+ // 案例 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>`;
|
|
|
}
|
|
}
|
|
|
- const mw = ['用法', '案例', '缺点', '输入', '输出'].includes(c) ? 'min-width:140px;' : (c === '工具名称' ? 'min-width:100px;font-weight:600;white-space:nowrap;' : 'white-space:nowrap;');
|
|
|
|
|
- return `<td style="${tdBase}${mw}">${inner}</td>`;
|
|
|
|
|
|
|
+ return `<tr class="${par}">${caseTds}</tr>`;
|
|
|
}).join('');
|
|
}).join('');
|
|
|
- return `<tr>${tds}</tr>`;
|
|
|
|
|
}).join('');
|
|
}).join('');
|
|
|
- return `<div style="overflow-x:auto;border:1px solid var(--line);border-radius:8px;">
|
|
|
|
|
- <table style="border-collapse:collapse;width:100%;min-width:900px;background:#fff;">
|
|
|
|
|
- <thead><tr>${th}</tr></thead><tbody>${rows}</tbody>
|
|
|
|
|
- </table></div>`;
|
|
|
|
|
|
|
+ return `<div class="fqe-ttwrap"><table class="fqe-tt fqe-tt2">${thead}<tbody>${rows}</tbody></table></div>`;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 渲染后标记真正溢出的单元格(才显示蒙版+可点击)
|
|
|
|
|
+ function _markClampedCells() {
|
|
|
|
|
+ document.querySelectorAll('#modalContentTools .fqe-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');
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function openDetail(i) {
|
|
function openDetail(i) {
|
|
|
const it = VIEW[i];
|
|
const it = VIEW[i];
|
|
|
detailDialog.dataset.activeIdx = i;
|
|
detailDialog.dataset.activeIdx = i;
|
|
|
|
|
+ toolVersion = null; // 新帖 → 工具 tab 默认看最新版本
|
|
|
currentPinnedScoreEl = null;
|
|
currentPinnedScoreEl = null;
|
|
|
document.getElementById("modalMeta").innerHTML = `<span class="platform p-${esc(it.platformKey)}">${esc(it.platform)}</span><span>Query ID: <b style="font-family: monospace; color: var(--amber); background: var(--soft-amber); border: 1px solid rgba(184, 121, 24, 0.2); padding: 1px 6px; border-radius: 3px; font-size: 11px; margin-right: 8px;">${esc(it.run)}</b></span><span>${esc(it.date)} · ${esc(it.engagement)} · 质量 ${esc(it.grade)} ${esc(it.qscore)}</span>`;
|
|
document.getElementById("modalMeta").innerHTML = `<span class="platform p-${esc(it.platformKey)}">${esc(it.platform)}</span><span>Query ID: <b style="font-family: monospace; color: var(--amber); background: var(--soft-amber); border: 1px solid rgba(184, 121, 24, 0.2); padding: 1px 6px; border-radius: 3px; font-size: 11px; margin-right: 8px;">${esc(it.run)}</b></span><span>${esc(it.date)} · ${esc(it.engagement)} · 质量 ${esc(it.grade)} ${esc(it.qscore)}</span>`;
|
|
|
document.getElementById("modalTitle").textContent = it.title;
|
|
document.getElementById("modalTitle").textContent = it.title;
|