|
|
@@ -56,6 +56,12 @@
|
|
|
.ktitle{font-size:14px;font-weight:600}
|
|
|
.krole{font-size:10.5px;padding:1px 6px;border-radius:3px;background:#f1f5f9;color:#64748b}
|
|
|
.kref{font-size:11px;color:#0e7490;cursor:pointer}.kref:hover{text-decoration:underline}
|
|
|
+ .srcbtn{cursor:pointer;color:#0e7490;margin-left:3px;user-select:none}.srcbtn:hover{color:#155e75}
|
|
|
+ .srccard{border:1px solid #e2e8f0;border-radius:6px;padding:8px 10px;margin:8px 0;background:#f8fafc}
|
|
|
+ .srccard .ci{font-size:11.5px;color:#6b7280;font-weight:600;margin-bottom:5px}
|
|
|
+ .srccard img{max-width:100%;max-height:360px;border-radius:5px;border:1px solid #e2e8f0;cursor:zoom-in}
|
|
|
+ .srccard video{max-width:100%;max-height:300px;border-radius:5px}
|
|
|
+ .srcread{font-size:12.5px;color:#1f2937;line-height:1.6;margin-top:6px;background:#fff;border-left:3px solid #0e7490;padding:6px 9px}
|
|
|
.kbiz{font-size:11px;padding:1px 7px;border-radius:3px;background:#fef3c7;color:#92400e}
|
|
|
.kacts{margin-left:auto;display:flex;gap:6px}
|
|
|
.kbody{padding:6px 14px 13px}
|
|
|
@@ -84,11 +90,15 @@
|
|
|
.act-chip{display:inline-block;font-size:11.5px;padding:1px 7px;border-radius:4px;background:#fff7ed;color:#9a3412;border:1px solid #fed7aa}
|
|
|
.dir-txt{font-size:11.5px;color:#33312c;line-height:1.65}.out-txt{font-size:11.5px;color:#166534}
|
|
|
.from{font-size:11px;color:#9ca3af}
|
|
|
- /* what 要素表 */
|
|
|
+ /* what/why 自由小节 */
|
|
|
.def{font-size:13px;background:#fdf4ff;border-left:3px solid #a21caf;border-radius:0 6px 6px 0;padding:8px 12px;margin:6px 0 10px}
|
|
|
+ .ksec{margin:9px 0;padding-left:11px;border-left:3px solid #e2e8f0}
|
|
|
+ .ksec-h{display:flex;align-items:center;gap:7px;margin-bottom:4px}
|
|
|
+ .ksec-t{font-size:13px;font-weight:600;color:#334155}
|
|
|
+ .ksec-f{font-size:10.5px;padding:1px 7px;border-radius:9px;background:#f1f5f9;color:#64748b}
|
|
|
+ .ksec-c{font-size:12.5px;line-height:1.65;color:#1f2937;margin-bottom:4px}
|
|
|
table.elem{border-collapse:collapse;width:100%;font-size:12.5px}
|
|
|
- table.elem th,table.elem td{border:1px solid #f0e6f5;padding:6px 10px;text-align:left;vertical-align:top}
|
|
|
- table.elem th{background:#fae8ff;color:#86198f;font-weight:600;width:130px}
|
|
|
+ table.elem td{border:1px solid #f0e6f5;padding:6px 10px;text-align:left;vertical-align:top}
|
|
|
table.elem td.el{font-weight:600;color:#86198f;background:#fdf4ff;width:130px}
|
|
|
/* why 论点卡 */
|
|
|
.why-row{display:flex;gap:10px;margin:7px 0}
|
|
|
@@ -173,7 +183,7 @@ function KScopes({list,onScope}){
|
|
|
{list.map((x,i)=><ScopeChip key={i} x={{...x}} onScope={onScope} showType={true}/>)}</div>;
|
|
|
}
|
|
|
|
|
|
-function HowTable({k,onScope,onPrompt}){
|
|
|
+function HowTable({k,onScope,onPrompt,onSource}){
|
|
|
const steps=k.steps||[];
|
|
|
const scOf=(s,t)=>(s.作用域||[]).filter(x=>x.scope_type===t);
|
|
|
return <div className="twrap"><table className="proc">
|
|
|
@@ -186,7 +196,7 @@ function HowTable({k,onScope,onPrompt}){
|
|
|
</thead>
|
|
|
<tbody>{steps.map((s,i)=><tr className="step" key={i}>
|
|
|
<td className="idx">{i+1}</td>
|
|
|
- <td className="intent">{s.intent}</td>
|
|
|
+ <td className="intent">{s.intent}{s.出处&&s.出处.length>0&&<span className="srcbtn" title={"看原文出处 "+s.出处.join(" ")} onClick={()=>onSource(s.出处)}>🔍</span>}</td>
|
|
|
<td className="cstage">{s.创作阶段&&<span className="cstage-chip">{s.创作阶段}</span>}</td>
|
|
|
<td className="act">{s.动作&&<span className="act-chip">{s.动作}</span>}</td>
|
|
|
<td className="dir"><span className="dir-txt">{s.directive}</span></td>
|
|
|
@@ -195,36 +205,43 @@ function HowTable({k,onScope,onPrompt}){
|
|
|
</tr>)}</tbody>
|
|
|
</table></div>;
|
|
|
}
|
|
|
+function KSection({b}){
|
|
|
+ const items=b.条目||[];
|
|
|
+ return <div className="ksec">
|
|
|
+ <div className="ksec-h">{b.小标题&&<span className="ksec-t">{b.小标题}</span>}{b.形式&&<span className="ksec-f">{b.形式}</span>}</div>
|
|
|
+ {b.内容&&<div className="ksec-c">{b.内容}</div>}
|
|
|
+ {items.length>0&&<table className="elem"><tbody>{items.map((c,i)=><tr key={i}><td className="el">{c.词||c.要素}</td><td>{c.说明}</td></tr>)}</tbody></table>}
|
|
|
+ </div>;
|
|
|
+}
|
|
|
function WhatCard({k,onScope}){
|
|
|
return <div>
|
|
|
{k.界定&&<div className="def">界定:{k.界定}</div>}
|
|
|
- <table className="elem"><thead><tr><th>要素</th><th>说明</th></tr></thead>
|
|
|
- <tbody>{(k.构成||[]).map((c,i)=><tr key={i}><td className="el">{c.要素}</td><td>{c.说明}</td></tr>)}</tbody></table>
|
|
|
+ {(k.主体||[]).map((b,i)=><KSection key={i} b={b}/>)}
|
|
|
<KScopes list={k.作用域} onScope={onScope}/>
|
|
|
</div>;
|
|
|
}
|
|
|
function WhyCard({k,onScope}){
|
|
|
return <div>
|
|
|
{k.主张&&<div className="why-row"><span className="why-lab wl-claim">主张</span><span className="why-txt">{k.主张}</span></div>}
|
|
|
- {k.依据&&<div className="why-row"><span className="why-lab wl-basis">依据</span><span className="why-txt">{k.依据}</span></div>}
|
|
|
- {k["对创作的影响"]&&<div className="why-row"><span className="why-lab wl-impact">影响</span><span className="why-txt">{k["对创作的影响"]}</span></div>}
|
|
|
+ {(k.支撑||[]).map((b,i)=><KSection key={i} b={b}/>)}
|
|
|
<KScopes list={k.作用域} onScope={onScope}/>
|
|
|
</div>;
|
|
|
}
|
|
|
|
|
|
-function KnowledgeCard({k,idx,onScope,onPrompt,onJson,payload,how_titles}){
|
|
|
+function KnowledgeCard({k,idx,onScope,onPrompt,onJson,onSource,payload,how_titles}){
|
|
|
const par=k.parent;
|
|
|
return <div className="kcard" id={"k-"+k.id}>
|
|
|
<div className="khead">
|
|
|
<span className="kno">第{idx+1}颗</span>
|
|
|
<span className={"ktype ty-"+k.type}>{TYPECN[k.type]}</span>
|
|
|
<span className="ktitle">{k.title}</span>
|
|
|
+ {k.出处&&k.出处.length>0&&<span className="kref" title={"看原文出处 "+k.出处.join(" ")} onClick={()=>onSource(k.出处)}>🔍 出处 {k.出处.join(" ")}</span>}
|
|
|
{par&&<span className="kref" onClick={()=>{var e=document.getElementById("k-"+par.how_id);if(e)e.scrollIntoView({behavior:"smooth",block:"center"});}}>↑ 出自「{how_titles[par.how_id]||par.how_id}」第{par.step}步</span>}
|
|
|
{(k.业务阶段||[]).map((b,i)=><span key={i} className="kbiz">业务阶段:{b}</span>)}
|
|
|
<span className="kacts">{payload&&<button className="lbtn" onClick={()=>onJson(payload)}>⚙️ 入库 payload</button>}</span>
|
|
|
</div>
|
|
|
<div className="kbody">
|
|
|
- {k.type==="how"&&<HowTable k={k} onScope={onScope} onPrompt={onPrompt}/>}
|
|
|
+ {k.type==="how"&&<HowTable k={k} onScope={onScope} onPrompt={onPrompt} onSource={onSource}/>}
|
|
|
{k.type==="what"&&<WhatCard k={k} onScope={onScope}/>}
|
|
|
{k.type==="why"&&<WhyCard k={k} onScope={onScope}/>}
|
|
|
</div>
|
|
|
@@ -238,6 +255,7 @@ function App(){
|
|
|
const [pm,setPm]=useState(null);
|
|
|
const [json,setJson]=useState(null);
|
|
|
const [zoom,setZoom]=useState(null);
|
|
|
+ const [src,setSrc]=useState(null);
|
|
|
const [hl,setHl]=useState(false);
|
|
|
useEffect(()=>{Promise.all([j("/frameworks.json"),j("/payloads.json").catch(()=>[])]).then(([f,pl])=>{
|
|
|
setItems((f.posts||[]).map(p=>({...p,payloads:(pl||[]).filter(x=>(x.source||{}).id===(p.source_id||("xhs_"+p.post_id)))})));});},[]);
|
|
|
@@ -255,6 +273,9 @@ function App(){
|
|
|
const how_titles={}; ks.forEach(k=>{if(k.type==="how")how_titles[k.id]=k.title;});
|
|
|
const payloadOf=k=>(it.payloads||[]).find(p=>p.title===k.title);
|
|
|
const media=it.media||{}; const imgs=media.images||[]; const videoUrl=media.video_url;
|
|
|
+ const cardsById={}; (it.cards||[]).forEach(c=>cardsById[c.index]=c);
|
|
|
+ const openSource=list=>{var cs=(list||[]).map(s=>{var m=String(s).match(/\d+/);return m?cardsById[parseInt(m[0])]:null;}).filter(Boolean);if(cs.length)setSrc({cards:cs});};
|
|
|
+ const fmtT=s=>{s=Math.floor(s||0);return Math.floor(s/60)+":"+String(s%60).padStart(2,"0");};
|
|
|
return <div className="app">
|
|
|
<header className="top">
|
|
|
<h1>创作知识的解构</h1>
|
|
|
@@ -272,11 +293,11 @@ function App(){
|
|
|
</div>
|
|
|
</details>
|
|
|
<div className="barhint" style={{display:"flex",gap:8,alignItems:"center"}}>
|
|
|
- <span>这一帖拆出 <b>{ks.length}</b> 颗知识(how 工序表 / what 要素表 / why 论点卡);组件颗标「出自某 how 第 N 步」可点跳。</span>
|
|
|
+ <span>这一帖拆出 <b>{ks.length}</b> 颗知识(how 工序表 / what 构成卡 / why 论点卡);组件颗标「出自某 how 第 N 步」可点跳。</span>
|
|
|
<button className={"lbtn"+(hl?" on":"")} onClick={()=>setHl(!hl)} style={{marginLeft:"auto"}}>高亮「新值」</button>
|
|
|
</div>
|
|
|
{ks.length===0 && <div className="kcard"><div className="kbody" style={{padding:"20px 16px",color:"#9ca3af",fontSize:"13px"}}>🚫 这帖<b style={{color:"#6b7280"}}>没有可提取的创作知识</b>(纯作品 / 展示 / 无关)——① 读懂时 <code>is_empty=true</code>,后面拆颗/归类/入库已自动跳过。</div></div>}
|
|
|
- {ks.map((k,i)=><KnowledgeCard key={i} k={k} idx={i} onScope={setScope} onPrompt={openPrompt} onJson={setJson} payload={payloadOf(k)} how_titles={how_titles}/>)}
|
|
|
+ {ks.map((k,i)=><KnowledgeCard key={i} k={k} idx={i} onScope={setScope} onPrompt={openPrompt} onJson={setJson} onSource={openSource} payload={payloadOf(k)} how_titles={how_titles}/>)}
|
|
|
</div>
|
|
|
{scope&&<div><div className="ov" onClick={()=>setScope(null)}></div>
|
|
|
<div className="drawer"><h2>作用域回扣详情<span className="x" onClick={()=>setScope(null)}>×</span></h2>
|
|
|
@@ -296,6 +317,14 @@ function App(){
|
|
|
{json&&<div className="modal" onClick={()=>setJson(null)}><div className="mbox" onClick={e=>e.stopPropagation()}>
|
|
|
<h2>⚙️ ingest payload(给机器看)<span className="x" onClick={()=>setJson(null)}>×</span></h2>
|
|
|
<div className="body"><pre>{JSON.stringify(json,null,2)}</pre></div></div></div>}
|
|
|
+ {src&&<div className="modal" onClick={()=>setSrc(null)}><div className="mbox" onClick={e=>e.stopPropagation()}>
|
|
|
+ <h2>🔍 原文出处(① 读到的卡片)<span className="x" onClick={()=>setSrc(null)}>×</span></h2>
|
|
|
+ <div className="body">{src.cards.map((c,i)=><div className="srccard" key={i}>
|
|
|
+ <div className="ci">卡片{c.index}{c.start!=null?(" · "+fmtT(c.start)+"–"+fmtT(c.end)):""}</div>
|
|
|
+ {c.image_url&&<img src={c.image_url} onClick={()=>setZoom(c.image_url)} onError={e=>e.target.style.display='none'}/>}
|
|
|
+ {c.video_url&&<video src={c.video_url+(c.start!=null?("#t="+Math.floor(c.start)+","+Math.floor(c.end)):"")} controls/>}
|
|
|
+ {c.content?<div className="srcread">① 在这张卡读到的:{c.content}</div>:<div style={{color:"#9ca3af",fontSize:"12px",marginTop:5}}>(这张卡没读到要点)</div>}
|
|
|
+ </div>)}</div></div></div>}
|
|
|
{zoom&&<div className="lb" onClick={()=>setZoom(null)}><img src={zoom}/></div>}
|
|
|
</div>;
|
|
|
}
|