|
@@ -371,6 +371,13 @@
|
|
|
.steps .inf { background: #fdf6e3 !important; position: relative; outline: 1px dashed #c9a227; outline-offset: -2px; }
|
|
.steps .inf { background: #fdf6e3 !important; position: relative; outline: 1px dashed #c9a227; outline-offset: -2px; }
|
|
|
.steps .inf .ib { position: absolute; top: -1px; right: -1px; background: #c9a227; color: #fff; font-size: 9px; padding: 0 4px; border-radius: 0 0 0 4px; font-weight: 700; }
|
|
.steps .inf .ib { position: absolute; top: -1px; right: -1px; background: #c9a227; color: #fff; font-size: 9px; padding: 0 4px; border-radius: 0 0 0 4px; font-weight: 700; }
|
|
|
.steps-empty { padding: 12px; color: var(--text-muted); font-size: 12px; }
|
|
.steps-empty { padding: 12px; color: var(--text-muted); font-size: 12px; }
|
|
|
|
|
+ /* 归类命中(实质/形式):原值 → 命中值 逐行配对 (口径同 index.html renderSF) */
|
|
|
|
|
+ .steps .sf-map { display: flex; flex-direction: column; gap: 4px; }
|
|
|
|
|
+ .steps .sf-pair { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; line-height: 1.5; }
|
|
|
|
|
+ .steps .sf-old { color: #9aa0a6; text-decoration: line-through; }
|
|
|
|
|
+ .steps .sf-plain { color: var(--text); }
|
|
|
|
|
+ .steps .sf-arrow { color: #2e9e5b; flex: none; }
|
|
|
|
|
+ .steps .sf-new { display: inline-block; padding: 1px 7px; border-radius: 10px; background: #e3f3e8; color: #2e6b45; border: 1px solid #bfe3cb; font-weight: 600; white-space: nowrap; }
|
|
|
/* 输入/输出「值」单元格:超过 4 行加蒙版,点击展开/收起 */
|
|
/* 输入/输出「值」单元格:超过 4 行加蒙版,点击展开/收起 */
|
|
|
.clamp-val { position: relative; }
|
|
.clamp-val { position: relative; }
|
|
|
.clamp-val.clampable { max-height: 6.6em; overflow: hidden; cursor: zoom-in; }
|
|
.clamp-val.clampable { max-height: 6.6em; overflow: hidden; cursor: zoom-in; }
|
|
@@ -924,6 +931,61 @@ function markStepClamps() {
|
|
|
function fmtSF(v) {
|
|
function fmtSF(v) {
|
|
|
return v == null ? '' : Array.isArray(v) ? v.join('、') : v;
|
|
return v == null ? '' : Array.isArray(v) ? v.join('、') : v;
|
|
|
}
|
|
}
|
|
|
|
|
+const SF_ARROW =
|
|
|
|
|
+ '<svg class="sf-arrow" viewBox="0 0 24 24" width="13" height="13" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
|
|
|
+const SF_NO_MATCH = '无'; // 后端未命中占位符(与 category_match.py NO_MATCH 一致)
|
|
|
|
|
+/* 原值拆分:与后端 _split_values 一致 —— 括号内的「、」不拆、去重保序,
|
|
|
|
|
+ 保证原值子项与 *Match 子项「等长等序」可按下标配对 */
|
|
|
|
|
+function _splitParts(raw) {
|
|
|
|
|
+ if (raw == null) return [];
|
|
|
|
|
+ if (Array.isArray(raw)) {
|
|
|
|
|
+ const out = [], seen = new Set();
|
|
|
|
|
+ for (const x of raw) {
|
|
|
|
|
+ const p = String(x).trim();
|
|
|
|
|
+ if (p && !seen.has(p)) { seen.add(p); out.push(p); }
|
|
|
|
|
+ }
|
|
|
|
|
+ return out;
|
|
|
|
|
+ }
|
|
|
|
|
+ const parts = [];
|
|
|
|
|
+ let cur = '', depth = 0;
|
|
|
|
|
+ for (const ch of String(raw)) {
|
|
|
|
|
+ if (ch === '(' || ch === '(') { depth++; cur += ch; }
|
|
|
|
|
+ else if (ch === ')' || ch === ')') { depth--; cur += ch; }
|
|
|
|
|
+ else if (ch === '、' && depth === 0) { const p = cur.trim(); if (p) parts.push(p); cur = ''; }
|
|
|
|
|
+ else { cur += ch; }
|
|
|
|
|
+ }
|
|
|
|
|
+ const last = cur.trim();
|
|
|
|
|
+ if (last) parts.push(last);
|
|
|
|
|
+ const out = [], seen = new Set();
|
|
|
|
|
+ for (const p of parts) if (!seen.has(p)) { seen.add(p); out.push(p); }
|
|
|
|
|
+ return out;
|
|
|
|
|
+}
|
|
|
|
|
+/* *Match 拆分:按下标对齐,**不去重不滤空**,保留「无」占位 */
|
|
|
|
|
+function _matchParts(v) {
|
|
|
|
|
+ return v == null ? [] : String(v).split('、').map((x) => x.trim());
|
|
|
|
|
+}
|
|
|
|
|
+/* 实质/形式单元格:逐子项「原值 → 命中值」配对。
|
|
|
|
|
+ 命中 → 原值灰色划除 + 箭头 + 绿色命中值;
|
|
|
|
|
+ 未命中(占位「无」/缺失)→ 只显原值,黑色正常 */
|
|
|
|
|
+function renderSF(value, match) {
|
|
|
|
|
+ const olds = _splitParts(value);
|
|
|
|
|
+ const news = _matchParts(match);
|
|
|
|
|
+ if (!news.length) return esc(fmtSF(value)); // 整格未归类(旧数据无 *Match)→ 原值黑色
|
|
|
|
|
+ const n = Math.max(olds.length, news.length);
|
|
|
|
|
+ let rows = '';
|
|
|
|
|
+ for (let i = 0; i < n; i++) {
|
|
|
|
|
+ const a = olds[i], b = news[i];
|
|
|
|
|
+ const matched = b != null && b !== '' && b !== SF_NO_MATCH;
|
|
|
|
|
+ if (matched) {
|
|
|
|
|
+ rows += `<div class="sf-pair">${
|
|
|
|
|
+ a != null ? `<span class="sf-old">${esc(a)}</span>${SF_ARROW}` : ''
|
|
|
|
|
+ }<span class="sf-new">${esc(b)}</span></div>`;
|
|
|
|
|
+ } else if (a != null) {
|
|
|
|
|
+ rows += `<div class="sf-pair"><span class="sf-plain">${esc(a)}</span></div>`;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return `<div class="sf-map">${rows}</div>`;
|
|
|
|
|
+}
|
|
|
function ioCell(x, kind) {
|
|
function ioCell(x, kind) {
|
|
|
const cls = kind === 'in' ? 'c-in' : 'c-out';
|
|
const cls = kind === 'in' ? 'c-in' : 'c-out';
|
|
|
if (!x) return `<td class="${cls}"></td><td class="${cls}"></td><td class="${cls}"></td>`;
|
|
if (!x) return `<td class="${cls}"></td><td class="${cls}"></td><td class="${cls}"></td>`;
|
|
@@ -946,13 +1008,14 @@ function renderSteps(steps) {
|
|
|
rows += `<td rowspan="${n}" class="sid">${esc(s.id || '')}</td>
|
|
rows += `<td rowspan="${n}" class="sid">${esc(s.id || '')}</td>
|
|
|
<td rowspan="${n}"><div class="intent-text">${renderIntent(s.intent || s.directive || '')}</div></td>
|
|
<td rowspan="${n}"><div class="intent-text">${renderIntent(s.intent || s.directive || '')}</div></td>
|
|
|
<td rowspan="${n}">${s.effect ? `<span class="pill navy">${esc(s.effect)}</span>` : ''}</td>
|
|
<td rowspan="${n}">${s.effect ? `<span class="pill navy">${esc(s.effect)}</span>` : ''}</td>
|
|
|
- <td rowspan="${n}">${esc(fmtSF(s.substance))}</td>
|
|
|
|
|
- <td rowspan="${n}">${esc(fmtSF(s.form))}</td>`;
|
|
|
|
|
|
|
+ <td rowspan="${n}">${renderSF(s.substance, s.substanceMatch)}</td>
|
|
|
|
|
+ <td rowspan="${n}">${renderSF(s.form, s.formMatch)}</td>`;
|
|
|
}
|
|
}
|
|
|
rows += ioCell(ins[i], 'in');
|
|
rows += ioCell(ins[i], 'in');
|
|
|
if (i === 0) {
|
|
if (i === 0) {
|
|
|
rows += `<td rowspan="${n}">${s.via ? `<span class="pill teal">${esc(s.via)}</span>` : ''}</td>
|
|
rows += `<td rowspan="${n}">${s.via ? `<span class="pill teal">${esc(s.via)}</span>` : ''}</td>
|
|
|
- <td rowspan="${n}" class="vtxt">${esc(s.action || '')}</td>`;
|
|
|
|
|
|
|
+ <td rowspan="${n}" class="vtxt">${esc(s.action || '')}</td>
|
|
|
|
|
+ <td rowspan="${n}" class="vtxt">${esc(s.directive || '')}</td>`;
|
|
|
}
|
|
}
|
|
|
rows += ioCell(outs[i], 'out');
|
|
rows += ioCell(outs[i], 'out');
|
|
|
rows += '</tr>';
|
|
rows += '</tr>';
|
|
@@ -963,15 +1026,15 @@ function renderSteps(steps) {
|
|
|
<col style="width:44px"><col style="width:200px"><col style="width:92px">
|
|
<col style="width:44px"><col style="width:200px"><col style="width:92px">
|
|
|
<col style="width:112px"><col style="width:100px">
|
|
<col style="width:112px"><col style="width:100px">
|
|
|
<col style="width:112px"><col style="width:330px"><col style="width:92px">
|
|
<col style="width:112px"><col style="width:330px"><col style="width:92px">
|
|
|
- <col style="width:118px"><col style="width:130px">
|
|
|
|
|
|
|
+ <col style="width:118px"><col style="width:130px"><col style="width:130px">
|
|
|
<col style="width:112px"><col style="width:360px"><col style="width:110px">
|
|
<col style="width:112px"><col style="width:360px"><col style="width:110px">
|
|
|
</colgroup>
|
|
</colgroup>
|
|
|
<thead>
|
|
<thead>
|
|
|
- <tr><th class="h-req" colspan="5">需 求</th><th class="h-in" colspan="3">输 入</th><th class="h-im" colspan="2">实 现</th><th class="h-out" colspan="3">输 出</th></tr>
|
|
|
|
|
|
|
+ <tr><th class="h-req" colspan="5">需 求</th><th class="h-in" colspan="3">输 入</th><th class="h-im" colspan="3">实 现</th><th class="h-out" colspan="3">输 出</th></tr>
|
|
|
<tr>
|
|
<tr>
|
|
|
<th class="h-req2">#</th><th class="h-req2">目的</th><th class="h-req2">作用</th><th class="h-req2">实质</th><th class="h-req2">形式</th>
|
|
<th class="h-req2">#</th><th class="h-req2">目的</th><th class="h-req2">作用</th><th class="h-req2">实质</th><th class="h-req2">形式</th>
|
|
|
<th class="h-in2">类型</th><th class="h-in2">值</th><th class="h-in2">来源</th>
|
|
<th class="h-in2">类型</th><th class="h-in2">值</th><th class="h-in2">来源</th>
|
|
|
- <th class="h-im2">外部工具</th><th class="h-im2">动作</th>
|
|
|
|
|
|
|
+ <th class="h-im2">外部工具</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>
|
|
<th class="h-out2">类型</th><th class="h-out2">值</th><th class="h-out2">去处</th>
|
|
|
</tr>
|
|
</tr>
|
|
|
</thead><tbody>${rows}</tbody></table></div>`;
|
|
</thead><tbody>${rows}</tbody></table></div>`;
|