| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219 |
- <!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title>搜索评估 · 案例总览</title>
- <style>
- :root {
- --ink: #24211d;
- --muted: #6f6961;
- --line: #ded8ce;
- --paper: #fbfaf7;
- --panel: #ffffff;
- --mint: #1f8a70;
- --rose: #b24b63;
- --amber: #b87918;
- --cyan: #2a6f8f;
- --soft-mint: #e9f5f0;
- --soft-rose: #f8e9ee;
- --soft-amber: #fff2d9;
- --soft-cyan: #e7f2f7;
- --shadow: 0 18px 45px rgba(41, 35, 28, .08);
- }
- * { box-sizing: border-box; }
- body {
- margin: 0;
- color: var(--ink);
- background: var(--paper);
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
- line-height: 1.55;
- }
- header {
- padding: 40px 32px 22px;
- border-bottom: 1px solid var(--line);
- background: linear-gradient(180deg, #fff 0%, #fbfaf7 100%);
- }
- .wrap { max-width: 1220px; margin: 0 auto; }
- .eyebrow {
- display: flex;
- gap: 8px;
- align-items: center;
- color: var(--mint);
- font-size: 13px;
- font-weight: 700;
- text-transform: uppercase;
- letter-spacing: 0;
- }
- h1 {
- margin: 10px 0 10px;
- font-size: clamp(32px, 5vw, 58px);
- line-height: 1.06;
- letter-spacing: 0;
- }
- .lede {
- max-width: 860px;
- margin: 0;
- color: var(--muted);
- font-size: 17px;
- }
- .stats {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 12px;
- margin-top: 24px;
- }
- .stat {
- background: var(--panel);
- border: 1px solid var(--line);
- border-radius: 8px;
- padding: 14px;
- box-shadow: var(--shadow);
- min-height: 86px;
- }
- .stat strong { display: block; font-size: 26px; line-height: 1.1; }
- .stat span { color: var(--muted); font-size: 13px; }
- main { padding: 24px 32px 48px; }
- .toolbar {
- display: flex;
- gap: 10px;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 18px;
- flex-wrap: wrap;
- }
- .filters { display: flex; gap: 8px; flex-wrap: wrap; }
- button, select {
- border: 1px solid var(--line);
- background: #fff;
- color: var(--ink);
- border-radius: 8px;
- padding: 9px 12px;
- font: inherit;
- }
- button { cursor: pointer; }
- button.active {
- color: #fff;
- background: var(--ink);
- border-color: var(--ink);
- }
- select { min-width: 190px; }
- .grid {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 16px;
- }
- .result {
- min-height: 500px;
- background: var(--panel);
- border: 1px solid var(--line);
- border-radius: 8px;
- overflow: hidden;
- box-shadow: var(--shadow);
- display: flex;
- flex-direction: column;
- }
- .thumbs {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 2px;
- height: 150px;
- background: #eee7dc;
- overflow: hidden;
- }
- .thumbs img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- display: block;
- background: #eee7dc;
- }
- .thumbs img:first-child:nth-last-child(1) { grid-column: 1 / -1; }
- .body { padding: 15px; flex: 1; display: flex; flex-direction: column; }
- .meta {
- display: flex;
- justify-content: space-between;
- gap: 10px;
- color: var(--muted);
- font-size: 12px;
- margin-bottom: 8px;
- }
- .platform {
- color: #fff;
- border-radius: 999px;
- padding: 2px 8px;
- font-weight: 700;
- white-space: nowrap;
- }
- .p-xhs { background: var(--rose); }
- .p-gzh { background: var(--mint); }
- .p-x { background: var(--cyan); }
- h2 { margin: 0 0 8px; font-size: 18px; line-height: 1.25; letter-spacing: 0; }
- .excerpt {
- color: var(--muted);
- font-size: 13px;
- display: -webkit-box;
- -webkit-line-clamp: 5;
- -webkit-box-orient: vertical;
- overflow: hidden;
- }
- .tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0; }
- .tag {
- background: #f2eee7;
- border-radius: 999px;
- padding: 3px 8px;
- font-size: 12px;
- color: #514a42;
- }
- .scorebar { margin-top: auto; }
- .overall {
- display: flex;
- align-items: end;
- justify-content: space-between;
- border-top: 1px solid var(--line);
- padding-top: 12px;
- }
- .score {
- font-size: 36px;
- line-height: .9;
- font-weight: 800;
- }
- .decision { color: var(--mint); font-weight: 800; }
- .decision.discard { color: var(--amber); }
- .mini-bars {
- display: grid;
- grid-template-columns: repeat(5, 1fr);
- gap: 4px;
- margin-top: 10px;
- }
- .mini-bars span {
- height: 7px;
- border-radius: 999px;
- background: #eee;
- overflow: hidden;
- position: relative;
- }
- .mini-bars span::before {
- content: "";
- display: block;
- width: calc(var(--v) * 20%);
- height: 100%;
- background: var(--mint);
- }
- .group-snapshot {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 5px;
- margin-top: 10px;
- }
- .group-pill {
- border: 1px solid var(--line);
- border-radius: 8px;
- padding: 5px 6px;
- background: #fff;
- min-width: 0;
- }
- .group-pill span {
- display: block;
- color: var(--muted);
- font-size: 11px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .group-pill strong {
- display: block;
- font-size: 14px;
- line-height: 1.1;
- }
- .actions { display: flex; gap: 8px; margin-top: 12px; }
- .actions a, .actions button {
- flex: 1;
- text-align: center;
- text-decoration: none;
- color: var(--ink);
- background: #fff;
- border: 1px solid var(--line);
- border-radius: 8px;
- padding: 8px 10px;
- font-size: 13px;
- }
- dialog {
- width: min(980px, calc(100vw - 28px));
- max-height: calc(100vh - 32px);
- border: 1px solid var(--line);
- border-radius: 8px;
- padding: 0;
- box-shadow: 0 28px 90px rgba(0,0,0,.25);
- }
- dialog::backdrop { background: rgba(38, 33, 27, .42); }
- .modal-head {
- position: sticky;
- top: 0;
- background: #fff;
- border-bottom: 1px solid var(--line);
- padding: 16px;
- z-index: 2;
- display: flex;
- justify-content: space-between;
- gap: 14px;
- align-items: start;
- }
- .modal-head h3 { margin: 0; font-size: 20px; line-height: 1.25; }
- .modal-content {
- display: grid;
- grid-template-columns: 1.1fr .9fr;
- gap: 18px;
- padding: 16px;
- }
- .section-title { margin: 18px 0 8px; font-weight: 800; }
- .raw {
- white-space: pre-wrap;
- background: #faf7f1;
- border: 1px solid var(--line);
- border-radius: 8px;
- padding: 12px;
- max-height: 330px;
- overflow: auto;
- color: #3d3831;
- font-size: 13px;
- }
- .images {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 8px;
- }
- .images img {
- width: 100%;
- max-height: 260px;
- object-fit: contain;
- border: 1px solid var(--line);
- border-radius: 8px;
- background: #f1ece4;
- }
- .scores {
- display: grid;
- gap: 12px;
- }
- .score-group {
- border: 1px solid var(--line);
- border-radius: 8px;
- background: #fff;
- overflow: hidden;
- }
- .score-group-head {
- display: flex;
- justify-content: space-between;
- gap: 10px;
- align-items: center;
- padding: 10px 11px;
- background: #faf7f1;
- border-bottom: 1px solid var(--line);
- font-size: 13px;
- font-weight: 800;
- }
- .score-group-head small {
- color: var(--muted);
- font-weight: 700;
- white-space: nowrap;
- }
- .score-group-body {
- display: grid;
- gap: 8px;
- padding: 10px;
- }
- .score-row {
- display: grid;
- grid-template-columns: 128px 1fr 34px;
- gap: 10px;
- align-items: center;
- font-size: 13px;
- }
- .score-row.missing {
- color: #a39b91;
- }
- .score-row.missing .meter span {
- display: none;
- }
- .meter {
- height: 9px;
- border-radius: 999px;
- background: #eee7dc;
- overflow: hidden;
- }
- .meter span { display: block; height: 100%; width: calc(var(--v) * 20%); background: var(--rose); }
- .rubric-note {
- background: var(--soft-cyan);
- border-left: 4px solid var(--cyan);
- padding: 10px 12px;
- color: #254c5d;
- border-radius: 4px;
- font-size: 13px;
- }
- @media (max-width: 980px) {
- .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
- .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
- .modal-content { grid-template-columns: 1fr; }
- }
- @media (max-width: 640px) {
- header, main { padding-left: 16px; padding-right: 16px; }
- .grid, .stats { grid-template-columns: 1fr; }
- .toolbar { align-items: stretch; }
- select { width: 100%; }
- .result { min-height: auto; }
- .group-snapshot { grid-template-columns: repeat(2, minmax(0, 1fr)); }
- }
- /* Extra styles for interactive navigation & matrix */
- .stats{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;}
- .stats .stat{min-width:0;}
- .p-zhihu{background:#2a6f8f;} .p-x{background:#2a6f8f;} .p-bili{background:#b24b63;} .p-douyin{background:#24211d;}
- .p-sph{background:#07c160;} .p-youtube{background:#c4302b;} .p-github{background:#24292e;} .p-toutiao{background:#f04142;} .p-weibo{background:#e6162d;}
- .nav{display:flex;flex-direction:column;gap:8px;margin-bottom:14px;}
- .navrow{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
- .navlab{width:54px;flex:0 0 54px;color:var(--muted);font-size:12px;font-weight:700;text-transform:uppercase;}
- .navrow .tab{border:1px solid var(--line);background:#fff;color:var(--ink);border-radius:999px;padding:6px 14px;font-size:13px;cursor:pointer;}
- .navrow .tab.on{background:var(--ink);color:#fff;border-color:var(--ink);}
- .navrow .tab .q{font-family:ui-monospace,Menlo,monospace;} .navrow .tab small{color:var(--muted);margin-left:4px;} .navrow .tab.on small{color:#cfd8dc;}
- #navQ .qtab{max-width:100%;white-space:normal;text-align:left;line-height:1.4;}
- .navrow .tab{white-space:normal;}
- #refresh{cursor:pointer;}
-
- /* 1:1 Morandi Matrix Replicated Style Rules */
- .btn{padding:3px 10px;border-radius:14px;border:1px solid #d1d5db;background:#fff;cursor:pointer;font-size:.74rem;color:#444;}
- .g-form .btn.on{background:#4f46e5;border-color:#4f46e5;color:#fff;}
- .g-lens .btn.on{background:#be185d;border-color:#be185d;color:#fff;}
- .g-mod .btn.on{background:#2e7d32;border-color:#2e7d32;color:#fff;}
- .g-tool .btn.on{background:#c2410c;border-color:#c2410c;color:#fff;}
- .g-tier .btn.on{background:#374151;border-color:#374151;color:#fff;}
-
- .mxwrap{max-height:55vh;overflow:auto;border:1px solid var(--line);border-radius:0 0 8px 8px;margin-bottom:12px;background:#fff;position:relative;}
- table#comboMx{border-collapse:separate;border-spacing:0;background:#fff;font-size:.65rem;width:100%;}
- table#comboMx th, table#comboMx td{border-right:1px solid #f0f1f5;border-bottom:1px solid #f0f1f5;text-align:center;}
- table#comboMx thead th{position:sticky;background:#fff;}
- table#comboMx thead tr.l1 th{top:0;z-index:11;background:#eef2ff;color:#4338ca;font-weight:700;font-size:.72rem;height:20px;border-bottom:1px solid #c7d2fe;}
- table#comboMx thead tr.l2 th{top:20px;z-index:11;background:#f5f7ff;color:#6366f1;font-weight:600;font-size:.66rem;height:18px;border-bottom:1px solid #e0e7ff;}
- table#comboMx thead tr.leaf th{top:38px;z-index:9;background:#fff;color:#1f2937;font-size:.66rem;min-width:120px;max-width:120px;height:24px;padding:3px;}
- table#comboMx thead .corner{left:0;top:0;z-index:13;background:#fff;min-width:96px;}
- .l1div{border-left:3px solid #818cf8!important;} .l2div{border-left:1.5px solid #cbd5e8!important;}
-
- table#comboMx tbody th.rh{position:sticky;left:0;z-index:8;background:#fffaf0;color:#92580c;text-align:left;padding:4px 8px;font-weight:600;white-space:nowrap;min-width:96px;border-right:1px solid #e6e8ef;}
- table#comboMx tbody tr.l1row td{background:#eef2ff;color:#4338ca;font-weight:700;font-size:.7rem;text-align:left;padding:3px 8px;position:sticky;left:0;}
-
- td.cell{min-width:120px;max-width:120px;height:30px;padding:2px 5px;cursor:pointer;font-family:ui-monospace,Menlo,monospace;font-size:.65rem;color:#1f2937;text-align:left;line-height:1.25;border-left:4px solid transparent;overflow:hidden;position:relative;}
- td.cell:hover{box-shadow:inset 0 0 0 2px #f59e0b;}
- td.cell.t0{border-left-color:#e5e7eb;background:#fbfbfc;color:#aeb3bd;}
- td.cell.t1{border-left-color:#bbf7d0;}
- td.cell.t2{border-left-color:#4ade80;background:#f3fdf6;}
- td.cell.t3{border-left-color:#15803d;background:#ecfdf3;}
- td.cell.tNA{border-left-color:#fcd34d;background:repeating-linear-gradient(45deg,#fff,#fff 4px,#fef9ec 4px,#fef9ec 8px);color:#b6bac4;}
- td.cell.gq-cell{color:#15803d;font-weight:600;}
- td.cell.rowdim{opacity:.26;}
- td.cell.hide{visibility:hidden;}
- td.cell.sel{outline:2px solid var(--ink);outline-offset:-2px;font-weight:bold;}
- td.cell.rowsel,td.cell.colsel{box-shadow:inset 0 0 0 999px rgba(255,193,7,.12);}
- td.cell.rowsel.colsel{box-shadow:inset 0 0 0 999px rgba(255,193,7,.22);}
- th.rh.rowsel{background:rgba(255,193,7,.22);color:var(--ink);font-weight:700;}
- thead th.colsel{background:rgba(255,193,7,.22);color:var(--ink);font-weight:700;}
-
- /* Database hit badge inside matrix cells */
- td.cell .hit-badge{
- position:absolute; right:2px; top:2px; background:#10b981; color:#fff; font-size:9px; font-weight:bold; border-radius:3px; padding:0 3px; line-height:1.2; box-shadow:0 1px 2px rgba(0,0,0,0.1);
- }
-
- /* Pop-up floating panel styling */
- .pop{position:fixed;background:#fff;border:1px solid #d1d5db;border-radius:9px;box-shadow:0 10px 30px rgba(0,0,0,.18);padding:13px 15px;max-width:450px;z-index:200;display:none;font-size:.82rem;line-height:1.5;}
- .pop .pt{font-weight:700;color:#1f2937;margin-bottom:7px;display:flex;gap:6px;align-items:center;flex-wrap:wrap;padding-right:18px;}
- .pop .path{font-size:.66rem;padding:1px 6px;border-radius:3px;} .pop .path.a{background:#eef2ff;color:#4338ca;} .pop .path.t{background:#fef3c7;color:#92400e;}
- .pop .tier{font-size:.64rem;padding:1px 7px;border-radius:3px;font-weight:600;}
- .tb0{background:#e5e7eb;color:#6b7280;} .tb1{background:#dcfce7;color:#166534;} .tb2{background:#bbf7d0;color:#14532d;} .tb3{background:#15803d;color:#fff;} .tbNA{background:#fef3c7;color:#92400e;}
- .pop .reason{background:#fffbeb;border-left:3px solid #f59e0b;padding:7px 11px;border-radius:3px;color:#78350f;margin:7px 0;font-size:.8rem;}
- .pop .reason.inv{background:#fef2f2;border-left-color:#ef4444;color:#991b1b;}
- .pop .gq{background:#ecfdf3;border:1px solid #a7f3d0;border-radius:6px;padding:7px 11px;margin:7px 0;font-family:ui-monospace,Menlo,monospace;font-size:.84rem;color:#15803d;}
- .pop .gq .t{font-family:inherit;font-size:.64rem;color:#6b7280;display:block;margin-bottom:2px;}
- .pop .small{font-size:.7rem;color:#9aa0ad;margin:4px 0;}
- .pop .lbl{font-size:.66rem;color:#6b7280;font-weight:600;margin:9px 0 3px;}
- .pop ul{margin:0;padding:0;list-style:none;} .pop li{padding:3px 8px;background:#f9fafb;border-radius:4px;margin-bottom:3px;font-family:ui-monospace,Menlo,monospace;font-size:.78rem;color:#1f2937;}
- .pop li.gen{background:#eef0ff;color:#3730a3;} .pop li.cur{box-shadow:inset 0 0 0 2px #818cf8;} .pop li .fm{font-size:.62rem;color:#4f46e5;margin-right:6px;}
- .pop .note{font-size:.7rem;color:#2e7d32;margin-top:6px;}
- .pop .close{position:absolute;top:6px;right:9px;cursor:pointer;color:#9ca3af;font-size:1.15rem;}
- .fac{border:1px solid var(--line);background:#fff;border-radius:999px;padding:4px 11px;font-size:12px;cursor:pointer;color:var(--ink);}
- .fac.on{background:var(--mint);color:#fff;border-color:var(--mint);}
- .fac small{opacity:.65;margin-left:3px;}
- #navQ .qtab .hit{display:inline-block;margin-left:7px;background:var(--soft-mint);color:var(--mint);border-radius:999px;padding:0 8px;font-size:11px;font-weight:700;}
- #navQ .qtab.on .hit{background:rgba(255,255,255,.22);color:#fff;}
- </style>
- </head>
- <body>
- <header>
- <div class="wrap">
- <div class="eyebrow">Content Search · runs/ 实时 · query → 形式 → 渠道</div>
- <h1>搜索评估 · 案例总览</h1>
- <p class="lede" id="lede" style="margin:0">加载中…</p>
- <div class="stats" id="stats"></div>
- </div>
- </header>
- <main>
- <div class="wrap">
- <div class="nav">
- <div class="navrow" style="margin-bottom:4px;">
- <span class="navlab">组合矩阵</span>
- <span style="color:var(--muted);font-size:12px;flex:1">行=类型 列=动作 格子色=评分,角标=命中数。点击选组合且弹出详情。</span>
- <label style="font-size:12px;cursor:pointer;display:flex;align-items:center;gap:4px;color:var(--muted);user-select:none;margin-right:12px;">
- <input type="checkbox" id="showFullMx" onchange="renderMatrix()" checked> 显示完整矩阵(未打勾只看已命中)
- </label>
- <button id="refresh" onclick="loadData(true)">↻ 刷新 runs</button>
- </div>
- <div class="mx-header" style="background:#fff; border: 1px solid var(--line); border-radius: 8px 8px 0 0; padding:12px 16px 8px; border-bottom: none;">
- <h4 style="margin:0 0 4px; font-size:13px; color:#1f2937; display:flex; justify-content:space-between; align-items:center; font-weight:700;">
- <span>动作 × 类型 · 组合矩阵 <span style="font-weight:400;font-size:11px;color:#9aa0ad">基于 <b id="gm">gemini-3.1-flash-lite</b></span></span>
- <span class="legend" style="display:flex;gap:9px;align-items:center;color:#6b7280;font-size:11px;">
- <span><span class="sw" style="display:inline-block;width:10px;height:10px;border-radius:2px;background:#15803d;vertical-align:middle;margin-right:3px;"></span>高 · <b id="s3" style="color:#15803d;">0</b></span>
- <span><span class="sw" style="display:inline-block;width:10px;height:10px;border-radius:2px;background:#4ade80;vertical-align:middle;margin-right:3px;"></span>中 · <b id="s2" style="color:#4ade80;">0</b></span>
- <span><span class="sw" style="display:inline-block;width:10px;height:10px;border-radius:2px;background:#bbf7d0;vertical-align:middle;margin-right:3px;"></span>低 · <b id="s1" style="color:#10b981;">0</b></span>
- <span><span class="sw" style="display:inline-block;width:10px;height:10px;border-radius:2px;background:#e5e7eb;vertical-align:middle;margin-right:3px;"></span>无效 · <b id="s0" style="color:#9ca3af;">0</b></span>
- <span><span class="sw" style="display:inline-block;width:10px;height:10px;border-radius:2px;background:#fcd34d;vertical-align:middle;margin-right:3px;"></span>未判 · <b id="sna" style="color:#b87918;">0</b></span>
- </span>
- </h4>
- <div class="sub" style="font-size:11px;color:#6b7280;margin-bottom:8px;">
- 共 1350 格 · 形式①原词不替换 · ②句子套模板 · ③同义池替换。
- </div>
- <div class="ctl" style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;font-size:11px;border-top:1px solid #f3f4f6;padding-top:8px;">
- <span class="lab" style="color:#9aa0ad;font-weight:600;">query生成方式</span>
- <span class="g-form">
- <button class="btn on" data-k="form" data-v="A">①原词</button>
- <button class="btn" data-k="form" data-v="B">②句子</button>
- <button class="btn" data-k="form" data-v="C">③同义</button>
- </span>
- <span class="lab" style="color:#9aa0ad;font-weight:600;margin-left:8px;">知识类型</span>
- <span class="g-lens">
- <button class="btn on" data-k="lens" data-v="工序">工序</button>
- <button class="btn" data-k="lens" data-v="工具">工具</button>
- <button class="btn" data-k="lens" data-v="能力">能力</button>
- </span>
- <span class="lab" style="color:#9aa0ad;font-weight:600;margin-left:8px;">约束·工具类型</span>
- <span class="g-tool">
- <button class="btn on" data-k="tool" data-v="">无</button>
- <span id="toolBtns"></span>
- </span>
- <span class="lab" style="color:#9aa0ad;font-weight:600;margin-left:8px;">搜索优先级</span>
- <span class="g-tier">
- <button class="btn on" data-k="tier" data-v="0">全部</button>
- <button class="btn" data-k="tier" data-v="2">≥中</button>
- <button class="btn" data-k="tier" data-v="3">仅高</button>
- </span>
- </div>
- </div>
- <div class="mxwrap" style="max-height:55vh; overflow:auto; border:1px solid var(--line); border-radius: 0 0 8px 8px; margin-bottom:12px; background:#fff; position:relative;">
- <table id="comboMx"></table>
- </div>
-
- <div class="pop" id="pop"></div>
- <div class="navrow"><span class="navlab">渠道</span><div id="navC" style="display:flex;gap:8px;flex-wrap:wrap"></div></div>
- </div>
- <div class="toolbar">
- <div class="filters"></div>
- <button id="reevalBtn" onclick="reevalCurrentQuery()" title="只对当前 query 的所有 form/帖子复评(不重新搜索)">♻️ 重评当前 query</button>
- <select id="sort">
- <option value="score">按综合分排序</option>
- <option value="date">按发布时间排序</option>
- <option value="platform">按平台排序</option>
- </select>
- </div>
- <div class="grid" id="grid"></div>
- </div>
- </main>
- <dialog id="detailDialog">
- <div class="modal-head">
- <div>
- <div id="modalMeta" class="meta"></div>
- <h3 id="modalTitle"></h3>
- </div>
- <button onclick="detailDialog.close()">关闭</button>
- </div>
- <div class="modal-content">
- <section>
- <div class="rubric-note" id="modalReason"></div>
- <div class="section-title">抓取文本节选</div>
- <div class="raw" id="modalText"></div>
- <div class="section-title">图片预览</div>
- <div class="images" id="modalImages"></div>
- </section>
- <aside>
- <div class="section-title">评分详情</div>
- <div class="scores" id="modalScores"></div>
- <div class="section-title">类型 / 命中 query</div>
- <div class="tags" id="modalTags"></div>
- </aside>
- </div>
- </dialog>
- <script>
- let DATA={queries:[],actions:[],types:[],matrix:[]}, st={form:'A',lens:'工序',tools:[],tier:0,qi:0,fi:0,channel:"all"}, VIEW=[];
- function esc(s) { return (s === undefined || s === null ? "" : String(s)).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); }
-
- const ACTIONS=[{"name": "检索", "l1": "获取", "l2": "搜索"}, {"name": "下载", "l1": "获取", "l2": "搜索"}, {"name": "调取", "l1": "获取", "l2": "查询"}, {"name": "上传", "l1": "获取", "l2": "录入"}, {"name": "拍摄", "l1": "获取", "l2": "录入"}, {"name": "录音", "l1": "获取", "l2": "录入"}, {"name": "键入", "l1": "获取", "l2": "录入"}, {"name": "选取", "l1": "获取", "l2": "引用"}, {"name": "裁切", "l1": "提取", "l2": "物理提取"}, {"name": "抠取", "l1": "提取", "l2": "物理提取"}, {"name": "抽帧", "l1": "提取", "l2": "物理提取"}, {"name": "识别", "l1": "提取", "l2": "化学提取"}, {"name": "反推", "l1": "提取", "l2": "化学提取"}, {"name": "解构", "l1": "提取", "l2": "化学提取"}, {"name": "元素生成", "l1": "生成", "l2": "元素生成"}, {"name": "数组生成", "l1": "生成", "l2": "关系生成"}, {"name": "结构生成", "l1": "生成", "l2": "关系生成"}, {"name": "添加", "l1": "修改", "l2": "增"}, {"name": "叠加", "l1": "修改", "l2": "增"}, {"name": "抹除", "l1": "修改", "l2": "删"}, {"name": "剪除", "l1": "修改", "l2": "删"}, {"name": "重述", "l1": "修改", "l2": "变"}, {"name": "风格化", "l1": "修改", "l2": "变"}, {"name": "转换", "l1": "修改", "l2": "变"}, {"name": "替换", "l1": "修改", "l2": "变"}, {"name": "调整", "l1": "修改", "l2": "变"}, {"name": "增强", "l1": "修改", "l2": "变"}];
- const TYPES=[{"name": "提示词", "l1": "程序控制类型", "l2": "指令"}, {"name": "负向提示词", "l1": "程序控制类型", "l2": "指令"}, {"name": "描述", "l1": "程序控制类型", "l2": "指令"}, {"name": "生成参数", "l1": "程序控制类型", "l2": "参数"}, {"name": "规格参数", "l1": "程序控制类型", "l2": "参数"}, {"name": "模型权重", "l1": "程序控制类型", "l2": "参数"}, {"name": "评分", "l1": "程序控制类型", "l2": "评估"}, {"name": "评语", "l1": "程序控制类型", "l2": "评估"}, {"name": "工作流", "l1": "程序控制类型", "l2": "流程"}, {"name": "批处理", "l1": "程序控制类型", "l2": "流程"}, {"name": "数字人", "l1": "数据复用类型", "l2": "原子"}, {"name": "版式", "l1": "数据复用类型", "l2": "原子"}, {"name": "模板", "l1": "数据复用类型", "l2": "序列"}, {"name": "参考图", "l1": "内容类型", "l2": "素材/化学变化"}, {"name": "参考视频", "l1": "内容类型", "l2": "素材/化学变化"}, {"name": "参考音频", "l1": "内容类型", "l2": "素材/化学变化"}, {"name": "对标内容", "l1": "内容类型", "l2": "素材/化学变化"}, {"name": "分镜图", "l1": "内容类型", "l2": "素材/化学变化"}, {"name": "转场", "l1": "内容类型", "l2": "素材/化学变化"}, {"name": "蒙版", "l1": "内容类型", "l2": "素材/化学变化"}, {"name": "控制图", "l1": "内容类型", "l2": "素材/化学变化"}, {"name": "运动轨迹", "l1": "内容类型", "l2": "素材/化学变化"}, {"name": "滤镜", "l1": "内容类型", "l2": "素材/化学变化"}, {"name": "构图布局", "l1": "内容类型", "l2": "素材/化学变化"}, {"name": "截图", "l1": "内容类型", "l2": "素材/物理变化"}, {"name": "视频片段", "l1": "内容类型", "l2": "素材/物理变化"}, {"name": "转场片段", "l1": "内容类型", "l2": "素材/物理变化"}, {"name": "关键帧", "l1": "内容类型", "l2": "素材/物理变化"}, {"name": "音效", "l1": "内容类型", "l2": "素材/物理变化"}, {"name": "特效", "l1": "内容类型", "l2": "素材/物理变化"}, {"name": "大纲", "l1": "内容类型", "l2": "半成品/序列"}, {"name": "脚本", "l1": "内容类型", "l2": "半成品/序列"}, {"name": "分镜脚本", "l1": "内容类型", "l2": "半成品/序列"}, {"name": "剪辑脚本", "l1": "内容类型", "l2": "半成品/序列"}, {"name": "配音文案", "l1": "内容类型", "l2": "半成品/序列"}, {"name": "底图", "l1": "内容类型", "l2": "半成品/原子"}, {"name": "样图", "l1": "内容类型", "l2": "半成品/原子"}, {"name": "分镜视频", "l1": "内容类型", "l2": "半成品/原子"}, {"name": "图层组合", "l1": "内容类型", "l2": "半成品/组合"}, {"name": "拼图", "l1": "内容类型", "l2": "半成品/组合"}, {"name": "歌词", "l1": "内容类型", "l2": "准成品"}, {"name": "配音", "l1": "内容类型", "l2": "准成品"}, {"name": "BGM", "l1": "内容类型", "l2": "准成品"}, {"name": "字幕", "l1": "内容类型", "l2": "准成品"}, {"name": "标题", "l1": "内容类型", "l2": "准成品"}, {"name": "正文", "l1": "内容类型", "l2": "准成品"}, {"name": "成品图", "l1": "内容类型", "l2": "成品"}, {"name": "视频成品", "l1": "内容类型", "l2": "成品"}, {"name": "合成图", "l1": "内容类型", "l2": "成品"}, {"name": "知识库", "l1": "知识类型", "l2": "知识库"}];
- const POOLS={"action_leaves": {"检索": ["找", "搜", "哪里找", "在哪找", "搜索"], "下载": ["下载", "怎么下载", "获取", "导入"], "调取": ["复用", "调用", "套用", "加载"], "上传": ["上传", "导入", "怎么导入", "用自己的"], "拍摄": ["拍", "拍摄", "录制", "录屏", "怎么拍"], "录音": ["录音", "录", "怎么录", "录制"], "键入": ["写", "输入", "怎么写", "编写"], "选取": ["挑选", "怎么选", "筛选", "选"], "裁切": ["裁剪", "截取", "怎么裁", "切片"], "抠取": ["抠图", "抠", "分割", "怎么抠"], "抽帧": ["抽帧", "提取帧", "怎么抽帧", "导出帧"], "识别": ["识别", "检测", "OCR", "提取文字", "转录"], "反推": ["反推", "分析", "推断", "怎么反推"], "解构": ["拆解", "解构", "分析结构"], "元素生成": ["生成", "制作", "怎么做", "做", "创作"], "数组生成": ["批量生成", "生成多张", "怎么批量", "批量做"], "结构生成": ["生成", "搭建", "怎么生成", "构建"], "添加": ["添加", "加", "怎么加", "加上"], "叠加": ["叠加", "合成", "叠", "加"], "抹除": ["去除", "去掉", "怎么去掉", "抹除", "消除"], "剪除": ["剪掉", "删掉", "怎么剪", "截短"], "重述": ["改写", "润色", "怎么改", "重写"], "风格化": ["风格化", "转风格", "风格迁移", "怎么转风格"], "转换": ["转换", "转", "怎么转", "转格式"], "替换": ["替换", "换", "怎么换", "替换成"], "调整": ["调整", "调", "怎么调", "优化"], "增强": ["增强", "提升画质", "怎么增强", "修复", "超分"]}, "types": {"提示词": ["提示词", "prompt", "咒语"], "负向提示词": ["负向提示词", "反向提示词", "negative prompt"], "描述": ["描述", "描述词", "提示描述"], "生成参数": ["参数", "出图参数", "生成参数", "seed"], "规格参数": ["尺寸", "分辨率", "画幅", "规格"], "模型权重": ["模型", "LoRA", "底模", "checkpoint"], "评分": ["评分", "打分", "评级"], "评语": ["评语", "点评", "反馈", "修改意见"], "工作流": ["工作流", "workflow", "流程图", "节点"], "批处理": ["批处理", "批量", "batch"], "数字人": ["数字人", "虚拟人", "数字分身", "AI分身"], "版式": ["版式", "排版", "版面", "layout"], "模板": ["模板", "template", "套版"], "参考图": ["参考图", "参考", "垫图", "ref"], "参考视频": ["参考视频", "参考片", "参考素材"], "参考音频": ["参考音频", "参考音色", "音色样本"], "对标内容": ["对标", "爆款参考", "竞品", "标杆案例"], "分镜图": ["分镜图", "分镜", "故事板", "storyboard"], "转场": ["转场", "转场效果", "transition"], "蒙版": ["蒙版", "抠像", "遮罩", "mask"], "控制图": ["控制图", "ControlNet", "结构图", "线稿"], "运动轨迹": ["运镜", "运动轨迹", "运动笔刷", "motion brush"], "滤镜": ["滤镜", "filter", "调色", "LUT"], "构图布局": ["构图", "布局", "构图布局"], "截图": ["截图", "屏幕截图", "截屏"], "视频片段": ["视频片段", "片段", "素材", "clip"], "转场片段": ["转场片段", "转场素材"], "关键帧": ["关键帧", "帧", "视频帧"], "音效": ["音效", "声音", "SFX", "音效素材"], "特效": ["特效", "视频特效", "VFX", "effect"], "大纲": ["大纲", "内容大纲", "提纲"], "脚本": ["脚本", "文案脚本", "剧本"], "分镜脚本": ["分镜脚本", "分镜表", "分镜"], "剪辑脚本": ["剪辑脚本", "剪辑表", "卡点表", "时间轴"], "配音文案": ["配音文案", "口播文案", "旁白文案"], "底图": ["底图", "背景图", "打底图"], "样图": ["样图", "效果图", "候选图", "draft"], "分镜视频": ["分镜视频", "分镜片段"], "图层组合": ["图层组合", "图层", "图层合成"], "拼图": ["拼图", "九宫格", "长图", "对比图"], "歌词": ["歌词", "词", "lyrics"], "配音": ["配音", "旁白", "解说", "人声"], "BGM": ["BGM", "背景音乐", "配乐"], "字幕": ["字幕", "视频字幕", "字幕条"], "标题": ["标题", "文案标题", "爆款标题"], "正文": ["正文", "文案", "内容正文"], "成品图": ["成品图", "出图", "图", "海报"], "视频成品": ["视频成品", "成片", "视频"], "合成图": ["合成图", "拼合图", "融合图", "合成"], "知识库": ["知识库", "资料库", "knowledge base"]}, "knowledge": {"工序": {"单步": ["教程", "流程", "步骤", "怎么做", "方法", "教学"], "全程": ["完整流程", "全流程", "pipeline", "SOP"]}, "能力": {"标记": ["一键", "自动", "直出", "秒出"], "达成": ["同款", "复刻", "效果"], "载体": ["提示词", "参数", "预设", "公式"]}, "工具": {"发现": ["用什么软件", "用什么工具", "工具推荐", "哪个好用", "有哪些工具"]}}, "tool_type": {"AI 模型": ["AI"], "桌面 APP": ["软件", "电脑端"], "云端 Web": ["在线", "网页版"], "API·CLI": ["代码", "命令行"], "插件扩展": ["插件"]}};
- const TOOL_TYPES=["AI 模型", "桌面 APP", "云端 Web", "API·CLI", "插件扩展"];
- const GMODEL="gemini-3.1-flash-lite";
- const AL=POOLS.action_leaves, TP=POOLS.types, KN=POOLS.knowledge, TQ=POOLS.tool_type;
- function aPool(a){return AL[a]||[a];}
- function tPool(t){return TP[t]||[t];}
- function pick(arr,i){return arr[Math.min(i,arr.length-1)];}
- function toolPrefix(lens){ return (st.tools.length && lens!=='工具') ? st.tools.map(t=>TQ[t][0]).join('/')+' ' : ''; }
-
- function genForms(leaf,ty,lens,tq){
- tq=tq||''; const aP=aPool(leaf), tP=tPool(ty);
- const aNat=aP[0], tNat=tP[0], aSyn=pick(aP,1), tSyn=pick(tP,1), K=KN[lens];
- if(lens==='工序'){ const s=K['单步'];
- return [['①原词',`${tq}${leaf} ${ty} 流程`],['②句子',`${tq}怎么${aNat}${tNat}`],['③同义',`${tq}${aSyn} ${tSyn} ${pick(s,2)}`]]; }
- if(lens==='能力'){ const mk=K['标记'];
- return [['①原词',`${tq}${leaf} ${ty} 技巧`],['②句子',`${tq}有没有能${mk[0]}${aNat}${tNat}的功能`],['③同义',`${tq}${pick(mk,1)} ${aSyn}${tSyn}`]]; }
- const fd=K['发现'];
- return [['①原词',`${leaf} ${ty} 工具`],['②句子',`${aNat}${tNat}用什么软件好`],['③同义',`${aSyn} ${tSyn} ${pick(fd,2)}`]];
- }
- const FIDX={A:0,B:1,C:2};
- function genQ(leaf,ty){return genForms(leaf,ty,st.lens,toolPrefix(st.lens))[FIDX[st.form]][1];}
- // 通用维度 5 项(移除 production_relevance / recency:前者搬到过滤指标顶层,后者改硬指标 recency_hard)
- const commonLabels={relevance:"相关性",result_quality:"成品质量",credibility:"可信度",novelty_coverage:"新增覆盖",concrete_use_case:"具体用例",completeness:"流程完整",step_structure:"步骤结构",step_reproducibility:"可复现性",capability_definition:"能力定义",implementation_depth:"实现深度",boundary_failure_eval:"边界/踩坑",generality:"通用性",capability_coverage:"工具覆盖",effective_comparison:"有效对比",param_specificity:"参数具体",worked_example:"示例完整",version_limits:"限制说明"};
- // 过滤指标(独立组,数据从 it 顶层取、不在 it.scores;量程不同,用 filterMax 在 renderScoreGroup 归一化)
- const filterLabels={production_relevance:"制作相关性",recency_hard:"发布时效",overall:"综合均分"};
- const filterMax={production_relevance:3,recency_hard:3,overall:5};
- const scoreGroups=[
- {id:"filter",title:"过滤指标",short:"过滤",hint:"独立于通用维度·过滤逻辑待定",topLevelKeys:["production_relevance","recency_hard","overall"]},
- {id:"common",title:"通用维度",short:"通用",keys:["relevance","result_quality","credibility","novelty_coverage","concrete_use_case"]},
- {id:"procedure",title:"工序维度",short:"工序",keys:["completeness","step_structure","step_reproducibility"]},
- {id:"step",title:"步骤维度",short:"步骤",keys:["capability_definition","implementation_depth","boundary_failure_eval","generality"]},
- {id:"tool",title:"工具维度",short:"工具",keys:["capability_coverage","effective_comparison","param_specificity","worked_example","version_limits"]}
- ];
- const PLATC={xhs:"小红书",gzh:"公众号",zhihu:"知乎",x:"X",bili:"B站",douyin:"抖音",sph:"视频号",youtube:"YouTube",github:"GitHub",toutiao:"头条",weibo:"微博"};
- const FN={A:"原词",B:"句子",C:"同义"};
- const NOIMG="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400'%3E%3Crect width='600' height='400' fill='%23eee7dc'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%236f6961' font-size='28'%3ENo image%3C/text%3E%3C/svg%3E";
- function curForm(){return st.qi===-1?null:(DATA.queries[st.qi]?DATA.queries[st.qi].forms[st.fi]:null);}
- // groupAverage 支持 topLevelKeys(filter 组从 it 顶层取数据,不进 scores)
- function groupAverage(it,g){const keys=g.topLevelKeys||g.keys;const src=g.topLevelKeys?it:it.scores;const vs=keys.map(k=>src[k]).filter(Number.isFinite);return vs.length?vs.reduce((a,b)=>a+b,0)/vs.length:null;}
- function fmt(v){return v===null?"N/A":v.toFixed(1);}
- // filter 组的 pill 不取均分(分轴不同),直接展示 pr·rh·overall 三个数;其他组仍显示均分
- function groupSnapshot(it){return scoreGroups.map(g=>{
- if(g.id==='filter'){
- const pr=it.production_relevance,rh=it.recency_hard,ov=it.overall;
- const f=(v,max)=>(v==null||!Number.isFinite(v))?'-':(max===5?(typeof v==='number'?v.toFixed(1):v):v);
- return `<div class="group-pill" title="${g.hint||''}"><span>${g.short}</span><strong style="font-size:12px;">${f(pr,3)}·${f(rh,3)}·${f(ov,5)}</strong></div>`;
- }
- const a=groupAverage(it,g);
- return `<div class="group-pill"><span>${g.short}</span><strong>${fmt(a)}</strong></div>`;
- }).join("");}
- // filter 组:数据从 it 顶层、量程按 filterMax 归一化到 5 量程(meter 的 --v*20% 公式不动)
- function renderScoreGroup(it,g){
- const isFilter=g.id==='filter';
- const keys=g.topLevelKeys||g.keys;
- const src=isFilter?it:it.scores;
- const labels=isFilter?filterLabels:commonLabels;
- const rows=keys.map(k=>{
- const v=src[k];
- const m=!Number.isFinite(v);
- const max=isFilter?(filterMax[k]||5):5;
- const meterV=m?0:(v*5/max);
- const suffix=isFilter&&max!==5?`/${max}`:'';
- const valStr=m?'-':(typeof v==='number'?(Number.isInteger(v)?v:v.toFixed(1)):v);
-
- const reason = (!isFilter && it.score_reasons) ? it.score_reasons[k] : '';
- const infoIcon = reason ? `<span class="info-icon" onclick="pinScoreReason(this, '${esc(labels[k]||k)}', '${esc(k)}')" title="点击定格查看评判理由" style="margin-left: 5px; cursor: pointer; color: var(--muted); opacity: 0.7; font-size: 11px; font-weight: normal; user-select: none;">ⓘ</span>` : '';
-
- return `<div class="score-row ${m?'missing':''}"><span>${labels[k]||k}</span><div class="meter" style="--v:${meterV}"><span></span></div><strong style="display: inline-flex; align-items: center;">${valStr}${suffix}${infoIcon}</strong></div>`;
- }).join("");
- const headRight=isFilter?(g.hint?`<small>${g.hint}</small>`:''):`<small>均分 ${fmt(groupAverage(it,g))}</small>`;
- return `<section class="score-group"><div class="score-group-head"><span>${g.title}</span>${headRight}</div><div class="score-group-body">${rows}</div></section>`;
- }
- function curDims(){
- if(st.qi===-1) { if (openCell) { const ai = +openCell.dataset.ai, ti = +openCell.dataset.ti; return { type: TYPES[ti].name, action: ACTIONS[ai].name }; } return {}; }
- const q=DATA.queries[st.qi]; return (q&&q.dims)?q.dims:{};
- }
- function spans(arr,key){const o=[];let cur=null,s=0;arr.forEach((x,i)=>{if(x[key]!==cur){if(cur!==null)o.push([cur,s,i-s]);cur=x[key];s=i;}});o.push([cur,s,arr.length-s]);return o;}
- const l1sp=spans(ACTIONS,'l1'),l2sp=spans(ACTIONS,'l2');
- const l1Start=new Set(l1sp.map(s=>s[1])),l2Start=new Set(l2sp.map(s=>s[1]));
- function detectLens(q) {
- const text = (q.original_q || (q.forms && q.forms[0] && q.forms[0].query) || "").toLowerCase();
- if (/流程|步骤|教程|方法|教学|SOP|pipeline|工序/.test(text)) return '工序';
- if (/一键|自动|直出|秒出|同款|复刻|效果|技巧|能力/.test(text)) return '能力';
- if (/用什么|软件|工具|推荐|哪个好用|有哪些/.test(text)) return '工具';
- return '工序';
- }
- function findQuery(aName, tName, lens, tool) {
- const matches = DATA.queries.filter(q => {
- if (!q.dims || q.dims.action !== aName || q.dims.type !== tName) return false;
- const qLens = detectLens(q);
- if (qLens !== lens) return false;
- const hasToolConstraint = q.dims.constraint && q.dims.constraint.kind === "工具类型";
- if (tool) {
- if (!hasToolConstraint || q.dims.constraint.value !== tool) return false;
- } else {
- if (hasToolConstraint) return false;
- }
- return true;
- });
- if (matches.length > 0) {
- matches.sort((x, y) => (y.hits || 0) - (x.hits || 0));
- return matches[0];
- }
- return null;
- }
- function selectQueryByActiveCellAndControls(aName, tName) {
- const activeTool = st.tools[0] || null;
- let match = findQuery(aName, tName, st.lens, activeTool);
-
- if (match) {
- st.qi = DATA.queries.indexOf(match);
- const fi = match.forms.findIndex(f => f.form === st.form);
- st.fi = fi >= 0 ? fi : 0;
- st.selectedAction = aName;
- st.selectedType = tName;
- } else {
- // Fallback: search for any query matching active lens & tool constraint with hits
- const anyMatch = DATA.queries.find(q => {
- if (!q.dims || !q.dims.action || !q.dims.type) return false;
- const qLens = detectLens(q);
- if (qLens !== st.lens) return false;
- const hasToolConstraint = q.dims.constraint && q.dims.constraint.kind === "工具类型";
- if (activeTool) {
- if (!hasToolConstraint || q.dims.constraint.value !== activeTool) return false;
- } else {
- if (hasToolConstraint) return false;
- }
- return q.hits > 0;
- });
-
- if (anyMatch) {
- st.qi = DATA.queries.indexOf(anyMatch);
- const fi = anyMatch.forms.findIndex(f => f.form === st.form);
- st.fi = fi >= 0 ? fi : 0;
- st.selectedAction = anyMatch.dims.action;
- st.selectedType = anyMatch.dims.type;
- } else {
- st.qi = -1;
- st.fi = 0;
- }
- }
- }
- // 角标 hits = 当前 form 下被采纳(report)的条数,与下面卡片所列的 report 子集口径一致 (≤30)
- function formReport(q) {
- const f = q.forms && q.forms.find(x => x.form === st.form);
- return f ? (f.report || 0) : 0;
- }
- function getFilteredHitsMap() {
- const cm = {};
- const activeTool = st.tools[0] || null;
- DATA.queries.forEach((q, i) => {
- const d = q.dims;
- if (!d || !d.type || !d.action) return;
- // Filter by lens
- if (detectLens(q) !== st.lens) return;
- // Filter by active tool constraint
- const hasToolConstraint = d.constraint && d.constraint.kind === "工具类型";
- if (activeTool) {
- if (!hasToolConstraint || d.constraint.value !== activeTool) return;
- } else {
- if (hasToolConstraint) return;
- }
- const k = d.type + '|' + d.action;
- const h = formReport(q);
- if (!cm[k] || h > cm[k].hits) {
- cm[k] = { i, hits: h };
- }
- });
- return cm;
- }
- function renderMatrix(){
- const showFull = document.getElementById('showFullMx').checked;
- const cm = getFilteredHitsMap();
-
- const activeActions = showFull ? ACTIONS : ACTIONS.filter(a => TYPES.some(t => {
- const c = cm[t.name+'|'+a.name];
- return c && c.hits > 0;
- }));
- const activeTypes = showFull ? TYPES : TYPES.filter(t => ACTIONS.some(a => {
- const c = cm[t.name+'|'+a.name];
- return c && c.hits > 0;
- }));
-
- const displayActions = activeActions.length ? activeActions : ACTIONS;
- const displayTypes = activeTypes.length ? activeTypes : TYPES;
-
- const l1sp = spans(displayActions, 'l1'), l2sp = spans(displayActions, 'l2');
- const l1Start = new Set(l1sp.map(s => s[1])), l2Start = new Set(l2sp.map(s => s[1]));
-
- let h = '<thead><tr class="l1"><th class="corner" rowspan="3">类型 \ 动作</th>' + l1sp.map(([v, s, c]) => `<th colspan="${c}" class="l1div">${v}</th>`).join('') + '</tr>';
- h += '<tr class="l2">' + l2sp.map(([v, s, c]) => `<th colspan="${c}" class="${l1Start.has(s) ? 'l1div' : 'l2div'}">${v}</th>`).join('') + '</tr>';
- h += '<tr class="leaf">' + displayActions.map((a, i) => `<th data-ai="${ACTIONS.indexOf(a)}" class="${l1Start.has(i) ? 'l1div' : (l2Start.has(i) ? 'l2div' : '')}">${a.name}</th>`).join('') + '</tr></thead><tbody>';
-
- const typeCategories = ['程序控制类型', '数据复用类型', '内容类型', '知识类型'];
- typeCategories.forEach(l1 => {
- const catTypes = displayTypes.filter(t => t.l1 === l1);
- if (catTypes.length === 0) return;
- h += `<tr class="l1row"><td colspan="${displayActions.length + 1}">${l1}</td></tr>`;
- catTypes.forEach((t) => {
- h += `<tr data-type="${t.name}"><th class="rh" data-ti="${TYPES.indexOf(t)}">${t.name}</th>` + displayActions.map((a) => {
- const ai = ACTIONS.indexOf(a);
- const ti = TYPES.indexOf(t);
- const cell = (DATA.matrix && DATA.matrix[ai]) ? (DATA.matrix[ai][ti] || {}) : {};
- const s = cell.tier !== undefined ? cell.tier : cell.s;
- const cls = (s === null || s === undefined) ? 'tNA' : ('t' + s);
- const isSel = (st.selectedAction === a.name && st.selectedType === t.name) ? ' sel' : '';
- return `<td class="cell ${cls}${isSel}" data-ai="${ai}" data-ti="${ti}"></td>`;
- }).join('') + '</tr>';
- });
- });
- document.getElementById('comboMx').innerHTML = h + '</tbody>';
- refresh();
- applyCrosshair();
- }
- // 点中 cell 时浅高亮整行整列(含 row/col 表头),方便定位
- function applyCrosshair() {
- document.querySelectorAll('.rowsel,.colsel').forEach(el => el.classList.remove('rowsel','colsel'));
- const ai = st.selectedAction ? ACTIONS.findIndex(a => a.name === st.selectedAction) : -1;
- const ti = st.selectedType ? TYPES.findIndex(t => t.name === st.selectedType) : -1;
- if (ai < 0 && ti < 0) return;
- document.querySelectorAll(`#comboMx [data-ai="${ai}"]`).forEach(el => el.classList.add('colsel'));
- document.querySelectorAll(`#comboMx [data-ti="${ti}"]`).forEach(el => el.classList.add('rowsel'));
- }
- function refresh(){
- const cm = getFilteredHitsMap();
-
- document.querySelectorAll('tr[data-type]').forEach(tr=>{
- const ty=tr.dataset.type;
- tr.querySelectorAll('td.cell').forEach(td=>{
- const ai=+td.dataset.ai, ti=+td.dataset.ti, a=ACTIONS[ai];
- const cell=(DATA.matrix && DATA.matrix[ai]) ? (DATA.matrix[ai][ti]||{}) : {};
- const s=cell.tier!==undefined?cell.tier:cell.s;
- const c=cm[ty+'|'+a.name];
- const hits=c?c.hits:0;
-
- td.textContent = genQ(a.name,ty);
- if(hits > 0) {
- const badge = document.createElement('span');
- badge.className = 'hit-badge';
- badge.textContent = hits;
- td.appendChild(badge);
- }
-
- td.classList.toggle('hide',(s==null?0:s)<(+st.tier));
- td.title = `${ty} × ${a.name}\nGemini评分: ${(s===null||s===undefined)?'未判':['无效','低','中','高'][s]}\n当前 form(${st.form}) 命中: ${hits} 篇`;
- });
- });
- }
- const pop=document.getElementById('pop'); let openCell=null;
- function showPop(td,x,y){
- openCell=td;
- const ai=+td.dataset.ai,ti=+td.dataset.ti,a=ACTIONS[ai],t=TYPES[ti];
- const cell=(DATA.matrix && DATA.matrix[ai]) ? (DATA.matrix[ai][ti]||{}) : {};
- const s=cell.tier!==undefined?cell.tier:cell.s;
- const tn=(s==null)?'未判':['无效','低','中','高'][s], tb=(s==null)?'NA':s;
- const gen=genForms(a.name,t.name,st.lens,toolPrefix(st.lens));
-
- // Find matching query in database for this cell
- const activeTool = st.tools[0] || null;
- const matches = DATA.queries.filter(q => {
- if (!q.dims || q.dims.action !== a.name || q.dims.type !== t.name) return false;
- const qLens = detectLens(q);
- if (qLens !== st.lens) return false;
- const hasToolConstraint = q.dims.constraint && q.dims.constraint.kind === "工具类型";
- if (activeTool) {
- if (!hasToolConstraint || q.dims.constraint.value !== activeTool) return false;
- } else {
- if (hasToolConstraint) return false;
- }
- return true;
- });
-
- let dbQuery = null;
- if (matches.length > 0) {
- matches.sort((x, y) => (y.hits || 0) - (x.hits || 0));
- dbQuery = matches[0];
- }
-
- const dbFormMap = {};
- if (dbQuery) {
- dbQuery.forms.forEach(f => {
- dbFormMap[f.form] = f.query;
- });
- }
-
- const formKeys = ['A', 'B', 'C'];
- let html=`<span class="close">×</span>
- <div class="pt">
- <span class="path a">${a.l1}›${a.l2}›${a.name}</span>
- <span class="path t">${t.l1}›${t.name}</span>
- <span class="tier tb${tb}">gemini·${tn}</span>
- </div>
- <div class="reason${s===0?' inv':''}">${esc(cell.r||'(模型未给该格评分)')}</div>
- <div class="lbl">系统生成 · 知识类型=${st.lens}${st.tools.length?' · 工具类型='+st.tools.join('/'):''}</div>
- <ul>` + gen.map(([fName, genQStr], idx) => {
- const formKey = formKeys[idx];
- const actualQ = dbFormMap[formKey];
- const isCurrent = formKey === st.form;
- let content = `<span class="fm">${fName}</span>${esc(genQStr)}`;
- if (actualQ) {
- if (actualQ !== genQStr) {
- content += `<div style="font-size: 13px; margin-top: 6px; color: #047857; font-weight: 600; font-family: ui-monospace, Menlo, Monaco, Consolas, monospace; line-height: 1.45; background: #ecfdf5; border: 1px dashed #34d399; padding: 6px 10px; border-radius: 6px; width: 100%; box-sizing: border-box; display: flex; align-items: center; gap: 4px; box-shadow: inset 0 1px 2px rgba(4, 120, 87, 0.04);">
- <span style="background: #10b981; padding: 2px 6px; border-radius: 4px; font-size: 11px; color: #fff; font-weight: bold; font-family: -apple-system, sans-serif; white-space: nowrap;">实际搜索</span>
- <span style="word-break: break-all; font-family: inherit;">${esc(actualQ)}</span>
- </div>`;
- } else {
- content += `<div style="font-size: 13px; margin-top: 6px; color: #374151; font-weight: 600; font-family: ui-monospace, Menlo, Monaco, Consolas, monospace; line-height: 1.45; background: #f9fafb; border: 1px dashed #d1d5db; padding: 6px 10px; border-radius: 6px; width: 100%; box-sizing: border-box; display: flex; align-items: center; gap: 4px; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.02);">
- <span style="background: #9ca3af; padding: 2px 6px; border-radius: 4px; font-size: 11px; color: #fff; font-weight: bold; font-family: -apple-system, sans-serif; white-space: nowrap;">实际搜索</span>
- <span style="font-style: italic; font-family: inherit; color: #6b7280;">(同上)</span>
- </div>`;
- }
- }
- return `<li class="gen${isCurrent ? ' cur' : ''}" style="display: flex; flex-direction: column; align-items: flex-start; gap: 2px; padding: 6px 8px;">${content}</li>`;
- }).join('') + '</ul>';
-
- pop.innerHTML=html;
- pop.style.display='block';
- pop.style.left=Math.max(8,Math.min(x,window.innerWidth-466))+'px';
- pop.style.top=Math.max(8,Math.min(y,window.innerHeight-pop.offsetHeight-12))+'px';
- pop.querySelector('.close').onclick=()=>{
- pop.style.display='none';
- openCell=null;
- };
- }
- function computeStats() {
- const stat = {"0": 0, "1": 0, "2": 0, "3": 0, "na": 0};
- if (DATA.matrix) {
- DATA.matrix.forEach(row => row.forEach(cell => {
- const s = cell ? (cell.tier !== undefined ? cell.tier : cell.s) : null;
- if (s === null || s === undefined) stat["na"]++;
- else if (stat[s] !== undefined) stat[s]++;
- }));
- }
- document.getElementById('s3').textContent = stat['3'];
- document.getElementById('s2').textContent = stat['2'];
- document.getElementById('s1').textContent = stat['1'];
- document.getElementById('s0').textContent = stat['0'];
- document.getElementById('sna').textContent = stat['na'];
- }
- function renderFormsChan(){
- const q=st.qi===-1?null:DATA.queries[st.qi];
- if(!q) {
- document.getElementById("navC").innerHTML='';
- return;
- }
- const f=curForm();
- if(!f) {
- document.getElementById("navC").innerHTML='';
- return;
- }
- const chans=["all",...f.platforms];
- document.getElementById("navC").innerHTML=chans.map(c=>{
- const n=c==="all"?f.results.length:f.results.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>`;
- }).join("");
- }
- function renderNav(){ renderMatrix(); renderFormsChan(); }
- function renderHead(){
- const f=curForm();
- if(!f) return;
- document.getElementById("lede").textContent="";
- let it=f.results;
- if(st.channel!=="all") it=it.filter(r=>r.platformKey===st.channel);
- const valid=it.filter(r=>!r.anomaly);
- const rep=valid.filter(r=>r.decision==="report").length;
- const dis=valid.filter(r=>r.decision==="discard").length;
- const anom=it.filter(r=>r.anomaly).length;
- const avg=(valid.reduce((s,r)=>s+r.overall,0)/(valid.length||1)).toFixed(1);
- const lab=st.channel==="all"?"该形式结果数":(PLATC[st.channel]||st.channel)+" 结果数";
- document.getElementById("stats").innerHTML=[[it.length,lab],[rep,"建议上报"],[avg,"平均综合分 / 5"],[dis,"丢弃"],[anom,"异常"]].map(([n,l])=>`<div class="stat"><strong>${n}</strong><span>${l}</span></div>`).join("");
- }
- // POST /api/reeval —— 后台只对当前 query 的所有 form 文件复评(不重新搜索);
- // server.py 立即返回 {status:'started', pid, log},前端只显示状态、不轮询,刷新页面看新数据。
- function reevalCurrentQuery(){
- if (st.qi === -1 || !DATA.queries[st.qi]) { alert('请先选一个 query 再重评'); return; }
- const q = DATA.queries[st.qi].key;
- if (!confirm(`重评 ${q} 的所有帖子(A/B/C 三种 form)?\n约 1-3 分钟(视帖子数),过程中页面可继续浏览。\n完成后刷新页面看新数据。`)) return;
- const btn = document.getElementById('reevalBtn');
- const oldText = btn.textContent;
- btn.disabled = true; btn.textContent = '♻️ 提交中…';
- fetch('/api/reeval', {
- method: 'POST', headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({q}),
- }).then(r => r.json().then(d => ({ok: r.ok, d}))).then(({ok, d}) => {
- if (ok && d.status === 'started') {
- btn.textContent = `♻️ 重评中 ${q} (PID ${d.pid}) · 完成后刷新页面`;
- // 不自动恢复 —— 让按钮停留在"重评中"状态直到用户主动刷新;日志见 runs/${q}/_reeval.log
- } else {
- btn.disabled = false; btn.textContent = oldText;
- alert('启动失败:' + (d.error || JSON.stringify(d)));
- }
- }).catch(e => {
- btn.disabled = false; btn.textContent = oldText;
- alert('请求失败:' + e);
- });
- }
- function sortedItems(){
- const f=curForm();
- if(!f) return [];
- let it=f.results.slice();
- if(st.channel!=="all") it=it.filter(r=>r.platformKey===st.channel);
-
- const s=document.getElementById("sort").value;
- const decWeight = r => (r.anomaly ? 2 : (r.decision === "report" ? 0 : 1));
-
- it.sort((a,b)=>{
- const wA = decWeight(a), wB = decWeight(b);
- if (wA !== wB) return wA - wB;
- if(s==="score") return b.overall - a.overall;
- if(s==="date") return (b.date||"").localeCompare(b.date||"");
- if(s==="platform") return (a.platform||"").localeCompare(b.platform||"","zh-Hans") || b.overall - a.overall;
- return 0;
- });
- return it;
- }
- function renderGrid(){
- VIEW=sortedItems();
- document.getElementById("grid").innerHTML=VIEW.map((it,idx)=>{
- const imgs=it.images.length?it.images.slice(0,3).map(s=>`<img src="${esc(s)}" referrerpolicy="no-referrer" loading="lazy" onerror="this.src='${NOIMG}'">`).join(""):`<img src="${NOIMG}">`;
- const bars=["relevance","result_quality","credibility","novelty_coverage","concrete_use_case"].map(k=>`<span title="${commonLabels[k]} ${it.scores[k]||0}" style="--v:${it.scores[k]||0}"></span>`).join("");
- return `<article class="result">
- <div class="thumbs">${imgs}</div>
- <div class="body">
- <div class="meta"><span class="platform p-${esc(it.platformKey)}">${esc(it.platform)}</span><span>${esc(it.date)} · ${esc(it.engagement)}</span></div>
- <h2>${esc(it.title)}</h2>
- <div class="excerpt">${esc(it.text)}</div>
- <div class="tags">${it.tools.slice(0,4).map(t=>`<span class="tag">${esc(t)}</span>`).join("")}</div>
- <div class="scorebar">
- <div class="overall">
- <div>
- <div class="score">${it.anomaly?'—':it.overall.toFixed(1)}</div>
- <small>综合分</small>
- </div>
- <div class="decision ${it.anomaly?'':it.decision}">${it.anomaly?'异常':esc(it.decision)}</div>
- </div>
- <div class="mini-bars">${bars}</div>
- <div class="group-snapshot">${groupSnapshot(it)}</div>
- <div class="actions">
- <button onclick="openDetail(${idx})">查看详情</button>
- <a href="${esc(it.url)}" target="_blank" rel="noreferrer">原链接</a>
- </div>
- </div>
- </div>
- </article>`;
- }).join("")||(st.qi===-1?'<p style="color:var(--muted);padding: 20px 0;text-align:center;grid-column: 1 / -1;">该组合暂无数据库扫描结果。</p>':'<p style="color:var(--muted);grid-column: 1 / -1;">该渠道无结果</p>');
- }
- function openDetail(i){
- const it=VIEW[i];
- detailDialog.dataset.activeIdx = i;
- currentPinnedScoreEl = null;
- document.getElementById("modalMeta").innerHTML=`<span class="platform p-${esc(it.platformKey)}">${esc(it.platform)}</span><span>${esc(it.date)} · ${esc(it.engagement)} · 质量 ${esc(it.grade)} ${esc(it.qscore)}</span>`;
- document.getElementById("modalTitle").textContent=it.title;
- document.getElementById("modalReason").textContent=it.reason;
- document.getElementById("modalText").textContent=it.text||"(无正文)";
- document.getElementById("modalImages").innerHTML=it.images.length?it.images.map(s=>`<img src="${esc(s)}" referrerpolicy="no-referrer" loading="lazy" onerror="this.style.opacity=.3">`).join(""):"<p>搜索详情未返回图片。</p>";
- document.getElementById("modalTags").innerHTML=[...it.knowledge_type.map(t=>"类型:"+(KTM[t]||t)),...it.found_by.map(q=>"命中:"+q)].map(t=>`<span class="tag">${esc(t)}</span>`).join("");
- document.getElementById("modalScores").innerHTML=scoreGroups.map(g=>renderScoreGroup(it,g)).join("");
- detailDialog.showModal();
- }
- let currentPinnedScoreEl = null;
- function pinScoreReason(el, label, k) {
- const modalReason = document.getElementById("modalReason");
- const activeIdx = detailDialog.dataset.activeIdx;
- const it = VIEW[activeIdx];
- if (!it || !it.score_reasons) return;
- const reason = it.score_reasons[k] || '';
-
- if (currentPinnedScoreEl === el) {
- modalReason.textContent = it.reason;
- modalReason.style.borderLeftColor = 'var(--cyan)';
- modalReason.style.background = 'var(--soft-cyan)';
- modalReason.style.color = '#254c5d';
- el.style.color = '';
- currentPinnedScoreEl = null;
- } else {
- if (currentPinnedScoreEl) {
- currentPinnedScoreEl.style.color = '';
- }
- modalReason.textContent = `【指标判定 - ${label}】\n${reason}`;
- modalReason.style.borderLeftColor = 'var(--amber)';
- modalReason.style.background = 'var(--soft-amber)';
- modalReason.style.color = '#78350f';
-
- el.style.color = 'var(--amber)';
- currentPinnedScoreEl = el;
- }
- }
- const KTM={procedure:"工序",step:"步骤",tool:"工具"};
- function rerender(mxClick){
- if (st.qi === -1) {
- document.getElementById("stats").innerHTML = "";
- document.getElementById("lede").textContent = "未找到对应的数据库 Query,请尝试切换筛选组合。";
- renderFormsChan();
- renderGrid();
- if (!mxClick) {
- document.querySelectorAll('td.cell.sel').forEach(x => x.classList.remove('sel'));
- }
- return;
- }
-
- if(st.qi>=DATA.queries.length) st.qi=0;
- if(st.fi>=DATA.queries[st.qi].forms.length) st.fi=0;
-
- if (!mxClick) {
- renderMatrix();
- }
- renderFormsChan();
- renderHead();
- renderGrid();
- }
-
- function loadData(keep){
- fetch("/api/data").then(r=>r.json()).then(d=>{
- DATA=d;
- if(!keep){
- st={form:'A',lens:'工序',tools:[],tier:0,qi:0,fi:0,channel:"all",selectedAction:null,selectedType:null};
- if(DATA.queries.length > 0) {
- const firstQ = DATA.queries[0];
- if(firstQ.dims) {
- st.selectedAction = firstQ.dims.action;
- st.selectedType = firstQ.dims.type;
- st.lens = detectLens(firstQ);
- }
- }
- }
-
- // Dynamically populate tool type buttons
- document.getElementById('toolBtns').innerHTML=TOOL_TYPES.map(t=>`<button class="btn" data-k="tool" data-v="${t}">${t}</button>`).join('');
-
- // Synchronize buttons states to st
- document.querySelectorAll('.ctl .btn').forEach(btn => {
- const k = btn.dataset.k, v = btn.dataset.v;
- if (MULTI[k]) {
- const ak = MULTI[k];
- btn.classList.toggle('on', v === '' ? st[ak].length === 0 : st[ak].includes(v));
- } else {
- btn.classList.toggle('on', st[k] === v);
- }
- });
- computeStats();
- if(st.selectedAction && st.selectedType) {
- selectQueryByActiveCellAndControls(st.selectedAction, st.selectedType);
- }
- rerender();
- });
- }
-
- // Matrix click listener (links matrix select to database select + opens pop-up)
- document.getElementById("comboMx").addEventListener("click", e=>{
- const td = e.target.closest("td.cell");
- if(!td) return;
-
- const ai = +td.dataset.ai, ti = +td.dataset.ti, a = ACTIONS[ai], t = TYPES[ti];
- st.selectedAction = a.name;
- st.selectedType = t.name;
-
- showPop(td, e.clientX, e.clientY);
-
- // Select the query
- selectQueryByActiveCellAndControls(a.name, t.name);
-
- document.querySelectorAll('td.cell.sel').forEach(x => x.classList.remove('sel'));
- td.classList.add('sel');
- applyCrosshair();
- rerender(true); // mxClick = true, updates results without redrawing full table
- });
-
- // Header controls event delegation
- const MULTI={tool:'tools'};
- document.querySelector('.ctl').addEventListener('click', e => {
- const b = e.target.closest('.btn');
- if (!b) return;
- const k = b.dataset.k, v = b.dataset.v, grp = document.querySelectorAll(`.ctl .btn[data-k="${k}"]`);
- if (MULTI[k]) {
- const ak = MULTI[k];
- if (v === '') {
- st[ak] = [];
- } else {
- if (st[ak].includes(v)) {
- st[ak] = [];
- } else {
- st[ak] = [v];
- }
- }
- grp.forEach(x => {
- const xv = x.dataset.v;
- x.classList.toggle('on', xv === '' ? st[ak].length === 0 : st[ak].includes(xv));
- });
- } else {
- grp.forEach(x => x.classList.remove('on'));
- b.classList.add('on');
- st[k] = v;
- }
-
- if(st.selectedAction && st.selectedType) {
- selectQueryByActiveCellAndControls(st.selectedAction, st.selectedType);
- }
-
- renderMatrix();
-
- if (openCell) {
- const ai = +openCell.dataset.ai, ti = +openCell.dataset.ti;
- const newCell = document.querySelector(`td.cell[data-ai="${ai}"][data-ti="${ti}"]`);
- if (newCell) {
- showPop(newCell, parseInt(pop.style.left), parseInt(pop.style.top));
- }
- }
-
- rerender(true);
- });
- // Close popups on clicking outside
- document.addEventListener('click', e => {
- if (!e.target.closest('td.cell') && !e.target.closest('.pop') && !e.target.closest('.btn')) {
- pop.style.display = 'none';
- openCell = null;
- }
- });
- document.getElementById("navC").addEventListener("click",e=>{const t=e.target.closest(".tab");if(!t)return;st.channel=t.dataset.c;renderNav();renderHead();renderGrid();});
- document.getElementById("sort").addEventListener("change",renderGrid);
- loadData();
- </script>
- </body>
- </html>
|