script.js 68 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902
  1. // 全局元素索引(将由Python动态注入)
  2. // const elementIndex = {};
  3. // 显示元素详情模态框
  4. function showElementDetail(elementId) {
  5. const elem = elementIndex[elementId];
  6. if (!elem) return;
  7. // 创建模态框
  8. const modal = document.createElement('div');
  9. modal.className = 'element-modal-backdrop';
  10. modal.onclick = function(e) {
  11. if (e.target === modal) {
  12. document.body.removeChild(modal);
  13. }
  14. };
  15. // 创建模态框内容
  16. const modalContent = document.createElement('div');
  17. modalContent.className = 'element-modal-content';
  18. modalContent.onclick = function(e) {
  19. e.stopPropagation();
  20. };
  21. // 构建内容
  22. let html = '<div class="modal-header">';
  23. html += '<h3>' + elem.name + '</h3>';
  24. html += '<button class="modal-close" onclick="this.closest(\'.element-modal-backdrop\').remove()">×</button>';
  25. html += '</div>';
  26. html += '<div class="modal-body">';
  27. // 描述
  28. if (elem.description) {
  29. html += '<div class="modal-section">';
  30. html += '<strong>描述:</strong>';
  31. html += '<p>' + elem.description + '</p>';
  32. html += '</div>';
  33. }
  34. // 类型和维度
  35. html += '<div class="modal-section">';
  36. html += '<strong>类型:</strong> ' + elem.type;
  37. if (elem.dimension && elem.dimension.一级) {
  38. html += ' / ' + elem.dimension.一级;
  39. if (elem.dimension.二级) {
  40. html += ' / ' + elem.dimension.二级;
  41. }
  42. }
  43. html += '</div>';
  44. // 分类
  45. if (elem.category) {
  46. html += '<div class="modal-section">';
  47. html += '<strong>分类:</strong>';
  48. if (typeof elem.category === 'object') {
  49. html += ' ' + (elem.category.一级分类 || '');
  50. if (elem.category.二级分类) {
  51. html += ' / ' + elem.category.二级分类;
  52. }
  53. } else {
  54. html += ' ' + elem.category;
  55. }
  56. html += '</div>';
  57. }
  58. // 跳转到Tab3查看完整信息
  59. html += '<div class="modal-footer">';
  60. html += '<button class="modal-btn" onclick="jumpToElement(\'' + elem.id + '\')">查看完整信息</button>';
  61. html += '</div>';
  62. html += '</div>';
  63. modalContent.innerHTML = html;
  64. modal.appendChild(modalContent);
  65. document.body.appendChild(modal);
  66. }
  67. // 跳转到元素详情(Tab3)
  68. function jumpToElement(elementId) {
  69. // 关闭模态框
  70. const modal = document.querySelector('.element-modal-backdrop');
  71. if (modal) {
  72. modal.remove();
  73. }
  74. // 切换到Tab3
  75. switchTab('tab3');
  76. // 等待DOM更新后滚动到元素
  77. setTimeout(function() {
  78. const elemItem = document.querySelector('[data-elem-id="' + elementId + '"]');
  79. if (elemItem) {
  80. // 展开所有父级容器
  81. let parent = elemItem.parentElement;
  82. while (parent) {
  83. if (parent.classList.contains('collapsed')) {
  84. parent.classList.remove('collapsed');
  85. }
  86. parent = parent.parentElement;
  87. }
  88. // 滚动到元素并高亮
  89. elemItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
  90. elemItem.classList.add('highlight-pulse');
  91. setTimeout(function() {
  92. elemItem.classList.remove('highlight-pulse');
  93. }, 2000);
  94. }
  95. }, 100);
  96. }
  97. // Tab切换功能
  98. function switchTab(tabId) {
  99. // 隐藏所有tab内容
  100. const allTabs = document.querySelectorAll('.tab-content');
  101. allTabs.forEach(tab => {
  102. tab.style.display = 'none';
  103. });
  104. // 移除所有tab的active类
  105. const allTabButtons = document.querySelectorAll('.tab');
  106. allTabButtons.forEach(btn => {
  107. btn.classList.remove('active');
  108. });
  109. // 显示选中的tab内容
  110. document.getElementById(tabId).style.display = 'block';
  111. // 给选中的tab按钮添加active类
  112. if (event && event.target) {
  113. event.target.classList.add('active');
  114. } else {
  115. // 程序化切换时,手动添加active类
  116. document.querySelectorAll('.tab').forEach((btn, idx) => {
  117. if ((tabId === 'tab1' && idx === 0) ||
  118. (tabId === 'tab2' && idx === 1) ||
  119. (tabId === 'tab3' && idx === 2) ||
  120. (tabId === 'tab4' && idx === 3) ||
  121. (tabId === 'tab5' && idx === 4)) {
  122. btn.classList.add('active');
  123. }
  124. });
  125. }
  126. // 如果切换到tab4,重新绘制连线
  127. if (tabId === 'tab4') {
  128. setTimeout(() => {
  129. drawAllConnections();
  130. }, 100);
  131. }
  132. // Tab5 不在切换时自动绘制连线,只在点击卡片时绘制
  133. }
  134. // 展开/收起段落功能
  135. function toggleCollapse(element) {
  136. const listItem = element.closest('.paragraph-item');
  137. if (!listItem.classList.contains('collapsible')) {
  138. return;
  139. }
  140. listItem.classList.toggle('collapsed');
  141. }
  142. // 展开/收起段落详细内容
  143. function toggleDetails(element) {
  144. const detailsContainer = element.closest('.paragraph-item')
  145. .querySelector('.paragraph-details');
  146. if (detailsContainer) {
  147. detailsContainer.classList.toggle('collapsed');
  148. const toggleBtn = element.closest('.paragraph-header')
  149. .querySelector('.details-toggle-btn');
  150. if (detailsContainer.classList.contains('collapsed')) {
  151. toggleBtn.innerHTML = '<span class="details-icon">▶</span> 查看详细内容';
  152. } else {
  153. toggleBtn.innerHTML = '<span class="details-icon">▶</span> 隐藏详细内容';
  154. }
  155. }
  156. }
  157. // Tab3: 展开/收起第一层级(实质/形式)
  158. function toggleLevel1(element) {
  159. element.classList.toggle('collapsed');
  160. }
  161. // Tab3: 展开/收起第二层级(具体元素/具体概念/抽象概念)
  162. function toggleLevel2(element) {
  163. element.classList.toggle('collapsed');
  164. }
  165. // Tab3: 展开/收起分类组
  166. function toggleCategoryGroup(element) {
  167. const categoryGroup = element.closest('.category-group');
  168. if (categoryGroup && categoryGroup.classList.contains('collapsible')) {
  169. categoryGroup.classList.toggle('collapsed');
  170. }
  171. }
  172. // Tab3: 展开/收起二级分类组
  173. function toggleSubcategoryGroup(element) {
  174. const subcategoryGroup = element.closest('.subcategory-group');
  175. if (subcategoryGroup && subcategoryGroup.classList.contains('collapsible')) {
  176. subcategoryGroup.classList.toggle('collapsed');
  177. }
  178. }
  179. // Tab3: 展开/收起元素详情
  180. function toggleElementDetails(element) {
  181. const elementItem = element.closest('.element-item');
  182. const detailsContainer = elementItem.querySelector('.element-details');
  183. if (detailsContainer) {
  184. event.stopPropagation();
  185. elementItem.classList.toggle('expanded');
  186. detailsContainer.classList.toggle('collapsed');
  187. }
  188. }
  189. // Tab3: 全部展开/收起所有层级
  190. function toggleAllLevels(expand) {
  191. // 第一层级
  192. const level1Headers = document.querySelectorAll('.level1-header');
  193. level1Headers.forEach(header => {
  194. if (expand) {
  195. header.classList.remove('collapsed');
  196. } else {
  197. header.classList.add('collapsed');
  198. }
  199. });
  200. // 第二层级
  201. const level2Headers = document.querySelectorAll('.level2-header');
  202. level2Headers.forEach(header => {
  203. if (expand) {
  204. header.classList.remove('collapsed');
  205. } else {
  206. header.classList.add('collapsed');
  207. }
  208. });
  209. // 分类组
  210. const categoryGroups = document.querySelectorAll('.category-group.collapsible');
  211. categoryGroups.forEach(group => {
  212. if (expand) {
  213. group.classList.remove('collapsed');
  214. } else {
  215. group.classList.add('collapsed');
  216. }
  217. });
  218. // 二级分类组
  219. const subcategoryGroups = document.querySelectorAll('.subcategory-group.collapsible');
  220. subcategoryGroups.forEach(group => {
  221. if (expand) {
  222. group.classList.remove('collapsed');
  223. } else {
  224. group.classList.add('collapsed');
  225. }
  226. });
  227. }
  228. // 旧版本兼容:保持toggleAllCategories函数
  229. function toggleAllCategories(expand) {
  230. toggleAllLevels(expand);
  231. }
  232. // 页面加载完成后初始化
  233. document.addEventListener('DOMContentLoaded', function() {
  234. console.log('脚本结果可视化页面已加载');
  235. // 初始化Tab4连线图
  236. if (document.getElementById('tab4')) {
  237. initializeRelationshipGraph();
  238. }
  239. });
  240. // ===== Tab4 关系图功能 =====
  241. // 初始化关系图
  242. function initializeRelationshipGraph() {
  243. // 延迟执行以确保DOM完全加载
  244. setTimeout(() => {
  245. // 初始状态:隐藏所有右侧节点
  246. resetRelationshipView();
  247. // 监听窗口大小变化,重新绘制连线
  248. window.addEventListener('resize', debounce(() => {
  249. if (selectedSubstanceId && relationshipData[selectedSubstanceId]) {
  250. drawSelectedSubstanceConnections(selectedSubstanceId, relationshipData[selectedSubstanceId]);
  251. }
  252. }, 300));
  253. }, 100);
  254. }
  255. // 绘制所有连线
  256. function drawAllConnections() {
  257. if (typeof relationshipData === 'undefined') {
  258. return;
  259. }
  260. const svg = document.getElementById('relationship-svg');
  261. if (!svg) return;
  262. // 清空现有连线
  263. svg.innerHTML = '';
  264. // 遍历所有实质点
  265. Object.keys(relationshipData).forEach(substanceId => {
  266. const relations = relationshipData[substanceId];
  267. // 绘制到灵感点的连线
  268. relations.inspiration.forEach(rel => {
  269. // 兼容新结构(支撑)和旧结构(相似度分数)
  270. const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
  271. drawConnection(substanceId, rel.target, 'inspiration', score);
  272. });
  273. // 绘制到目的点的连线
  274. relations.purpose.forEach(rel => {
  275. const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
  276. drawConnection(substanceId, rel.target, 'purpose', score);
  277. });
  278. // 绘制到关键点的连线
  279. relations.keypoint.forEach(rel => {
  280. const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
  281. drawConnection(substanceId, rel.target, 'keypoint', score);
  282. });
  283. });
  284. }
  285. // 绘制单条连线
  286. function drawConnection(sourceId, targetId, type, score) {
  287. const svg = document.getElementById('relationship-svg');
  288. if (!svg) return;
  289. const sourceNode = document.querySelector(`.substance-node[data-id="${sourceId}"]`);
  290. const targetNode = document.querySelector(`.target-node[data-id="${targetId}"]`);
  291. if (!sourceNode || !targetNode) return;
  292. // 获取节点位置
  293. const sourceRect = sourceNode.getBoundingClientRect();
  294. const targetRect = targetNode.getBoundingClientRect();
  295. const containerRect = svg.parentElement.getBoundingClientRect();
  296. // 计算相对于SVG容器的位置
  297. const x1 = sourceRect.right - containerRect.left;
  298. const y1 = sourceRect.top + sourceRect.height / 2 - containerRect.top;
  299. const x2 = targetRect.left - containerRect.left;
  300. const y2 = targetRect.top + targetRect.height / 2 - containerRect.top;
  301. // 创建贝塞尔曲线路径
  302. const midX = (x1 + x2) / 2;
  303. const path = `M ${x1} ${y1} Q ${midX} ${y1}, ${midX} ${(y1 + y2) / 2} T ${x2} ${y2}`;
  304. // 创建path元素
  305. const pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  306. pathElement.setAttribute('d', path);
  307. pathElement.setAttribute('class', `connection-line ${type}`);
  308. pathElement.setAttribute('fill', 'none');
  309. pathElement.setAttribute('data-source', sourceId);
  310. pathElement.setAttribute('data-target', targetId);
  311. pathElement.setAttribute('data-type', type);
  312. pathElement.setAttribute('data-score', score);
  313. svg.appendChild(pathElement);
  314. }
  315. // 高亮相关关系
  316. function highlightRelationships(substanceId) {
  317. if (typeof relationshipData === 'undefined') {
  318. return;
  319. }
  320. // 清除之前的高亮
  321. clearHighlights();
  322. // 高亮当前实质点
  323. const substanceNode = document.querySelector(`.substance-node[data-id="${substanceId}"]`);
  324. if (substanceNode) {
  325. substanceNode.classList.add('highlighted');
  326. }
  327. // 获取相关关系
  328. const relations = relationshipData[substanceId];
  329. if (!relations) return;
  330. // 高亮相关的目标点
  331. [...relations.inspiration, ...relations.purpose, ...relations.keypoint].forEach(rel => {
  332. const targetNode = document.querySelector(`.target-node[data-id="${rel.target}"]`);
  333. if (targetNode) {
  334. targetNode.classList.add('highlighted');
  335. }
  336. });
  337. // 高亮相关的连线
  338. const lines = document.querySelectorAll(`.connection-line[data-source="${substanceId}"]`);
  339. lines.forEach(line => {
  340. line.classList.add('highlighted');
  341. });
  342. }
  343. // 清除所有高亮
  344. function clearHighlights() {
  345. // 清除节点高亮
  346. document.querySelectorAll('.node.highlighted').forEach(node => {
  347. node.classList.remove('highlighted');
  348. });
  349. // 清除连线高亮
  350. document.querySelectorAll('.connection-line.highlighted').forEach(line => {
  351. line.classList.remove('highlighted');
  352. });
  353. }
  354. // 防抖函数
  355. function debounce(func, wait) {
  356. let timeout;
  357. return function executedFunction(...args) {
  358. const later = () => {
  359. clearTimeout(timeout);
  360. func(...args);
  361. };
  362. clearTimeout(timeout);
  363. timeout = setTimeout(later, wait);
  364. };
  365. }
  366. // ===== Tab4 新增:点击选择实质点功能 =====
  367. let selectedSubstanceId = null;
  368. // 选择实质点,展示关联关系
  369. function selectSubstance(substanceId) {
  370. if (typeof relationshipData === 'undefined') {
  371. return;
  372. }
  373. // 如果点击已选中的实质点,不做处理
  374. if (selectedSubstanceId === substanceId) {
  375. return;
  376. }
  377. selectedSubstanceId = substanceId;
  378. // 显示重置按钮
  379. const resetBtn = document.querySelector('.reset-btn');
  380. if (resetBtn) {
  381. resetBtn.style.display = 'block';
  382. }
  383. // 获取关系数据
  384. const relations = relationshipData[substanceId];
  385. if (!relations) return;
  386. // 收集所有关联的节点ID
  387. const relatedNodeIds = new Set();
  388. // 添加灵感点
  389. relations.inspiration.forEach(rel => {
  390. relatedNodeIds.add(rel.target);
  391. });
  392. // 添加目的点
  393. relations.purpose.forEach(rel => {
  394. relatedNodeIds.add(rel.target);
  395. });
  396. // 添加关键点
  397. relations.keypoint.forEach(rel => {
  398. relatedNodeIds.add(rel.target);
  399. });
  400. // 添加关联的实质点
  401. relations.substance.forEach(rel => {
  402. relatedNodeIds.add(rel.target);
  403. });
  404. // 高亮左侧选中的实质点
  405. document.querySelectorAll('.substance-node').forEach(node => {
  406. if (node.getAttribute('data-id') === substanceId) {
  407. node.classList.add('selected');
  408. node.classList.remove('dimmed');
  409. } else {
  410. node.classList.remove('selected');
  411. node.classList.add('dimmed');
  412. }
  413. });
  414. // 右侧节点:只显示关联的节点
  415. document.querySelectorAll('.target-node').forEach(node => {
  416. const nodeId = node.getAttribute('data-id');
  417. if (relatedNodeIds.has(nodeId)) {
  418. node.classList.add('visible');
  419. node.classList.remove('hidden');
  420. } else {
  421. node.classList.remove('visible');
  422. node.classList.add('hidden');
  423. }
  424. });
  425. // 根据右侧节点的可见性,控制section的显示
  426. document.querySelectorAll('.target-section').forEach(section => {
  427. const visibleNodes = section.querySelectorAll('.target-node.visible');
  428. if (visibleNodes.length > 0) {
  429. section.classList.add('has-visible-nodes');
  430. section.classList.remove('all-hidden');
  431. } else {
  432. section.classList.remove('has-visible-nodes');
  433. section.classList.add('all-hidden');
  434. }
  435. });
  436. // 重新绘制连线(只绘制选中实质点的连线)
  437. drawSelectedSubstanceConnections(substanceId, relations);
  438. }
  439. // 重置视图
  440. function resetRelationshipView() {
  441. selectedSubstanceId = null;
  442. // 隐藏重置按钮
  443. const resetBtn = document.querySelector('.reset-btn');
  444. if (resetBtn) {
  445. resetBtn.style.display = 'none';
  446. }
  447. // 左侧实质点:移除所有高亮和变暗
  448. document.querySelectorAll('.substance-node').forEach(node => {
  449. node.classList.remove('selected', 'dimmed');
  450. });
  451. // 右侧节点:隐藏所有节点
  452. document.querySelectorAll('.target-node').forEach(node => {
  453. node.classList.remove('visible');
  454. node.classList.add('hidden');
  455. });
  456. // 隐藏所有section
  457. document.querySelectorAll('.target-section').forEach(section => {
  458. section.classList.remove('has-visible-nodes');
  459. section.classList.add('all-hidden');
  460. });
  461. // 清空连线
  462. const svg = document.getElementById('relationship-svg');
  463. if (svg) {
  464. svg.innerHTML = '';
  465. }
  466. }
  467. // 绘制选中实质点的连线
  468. function drawSelectedSubstanceConnections(substanceId, relations) {
  469. const svg = document.getElementById('relationship-svg');
  470. if (!svg) return;
  471. // 清空现有连线
  472. svg.innerHTML = '';
  473. // 绘制到灵感点的连线
  474. relations.inspiration.forEach(rel => {
  475. // 兼容新结构(支撑)和旧结构(相似度分数)
  476. const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
  477. drawConnection(substanceId, rel.target, 'inspiration', score);
  478. });
  479. // 绘制到目的点的连线
  480. relations.purpose.forEach(rel => {
  481. const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
  482. drawConnection(substanceId, rel.target, 'purpose', score);
  483. });
  484. // 绘制到关键点的连线
  485. relations.keypoint.forEach(rel => {
  486. const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
  487. drawConnection(substanceId, rel.target, 'keypoint', score);
  488. });
  489. // 绘制到其他实质点的连线
  490. relations.substance.forEach(rel => {
  491. drawConnection(substanceId, rel.target, 'substance', rel.common_count / 10);
  492. });
  493. }
  494. // ===== Tab4 新版支撑关系图功能 =====
  495. let selectedSupportSubstanceId = null;
  496. let selectedSupportTargetId = null;
  497. // 选择实质点(新版)
  498. function selectSubstance(substanceId) {
  499. if (typeof supportRelationships === 'undefined') {
  500. console.error('supportRelationships not defined');
  501. return;
  502. }
  503. // 如果点击已选中的实质点,不做处理
  504. if (selectedSupportSubstanceId === substanceId) {
  505. return;
  506. }
  507. selectedSupportSubstanceId = substanceId;
  508. selectedSupportTargetId = null;
  509. // 清空画布
  510. clearSupportCanvas();
  511. // 获取关系数据
  512. const relations = supportRelationships.substance_to_target[substanceId];
  513. if (!relations) {
  514. console.error('No relations found for substance:', substanceId);
  515. return;
  516. }
  517. // 高亮选中的实质点,其他变暗
  518. document.querySelectorAll('.substance-card').forEach(card => {
  519. if (card.getAttribute('data-id') === substanceId) {
  520. card.classList.add('selected');
  521. card.classList.remove('dimmed');
  522. } else {
  523. card.classList.remove('selected');
  524. card.classList.add('dimmed');
  525. }
  526. });
  527. // 清除所有目标点的状态
  528. document.querySelectorAll('.target-card').forEach(card => {
  529. card.classList.remove('selected', 'dimmed', 'highlighted');
  530. });
  531. // 根据关系数据高亮相关的目标点
  532. const relatedTargets = new Set();
  533. relations.inspiration.forEach(rel => {
  534. relatedTargets.add(rel.target_id);
  535. });
  536. relations.purpose.forEach(rel => {
  537. relatedTargets.add(rel.target_id);
  538. });
  539. relations.keypoint.forEach(rel => {
  540. relatedTargets.add(rel.target_id);
  541. });
  542. // 高亮相关的目标点,其他变暗
  543. document.querySelectorAll('.target-card').forEach(card => {
  544. const cardId = card.getAttribute('data-id');
  545. if (relatedTargets.has(cardId)) {
  546. card.classList.add('highlighted');
  547. card.classList.remove('dimmed');
  548. } else {
  549. card.classList.add('dimmed');
  550. card.classList.remove('highlighted');
  551. }
  552. });
  553. // 绘制支撑关系(使用Sankey风格或树状结构)
  554. drawSupportRelationships(substanceId, relations);
  555. }
  556. // 选择目标点(反向查看)
  557. function selectTarget(targetType, targetIndex) {
  558. if (typeof supportRelationships === 'undefined') {
  559. console.error('supportRelationships not defined');
  560. return;
  561. }
  562. const targetId = `${targetType}-${targetIndex}`;
  563. // 如果点击已选中的目标点,不做处理
  564. if (selectedSupportTargetId === targetId) {
  565. return;
  566. }
  567. selectedSupportSubstanceId = null;
  568. selectedSupportTargetId = targetId;
  569. // 清空画布
  570. clearSupportCanvas();
  571. // 获取反向关系数据
  572. const relations = supportRelationships.target_to_substance[targetId];
  573. if (!relations) {
  574. console.error('No relations found for target:', targetId);
  575. return;
  576. }
  577. // 清除所有实质点的状态
  578. document.querySelectorAll('.substance-card').forEach(card => {
  579. card.classList.remove('selected', 'dimmed', 'highlighted');
  580. });
  581. // 高亮选中的目标点,其他变暗
  582. document.querySelectorAll('.target-card').forEach(card => {
  583. if (card.getAttribute('data-id') === targetId) {
  584. card.classList.add('selected');
  585. card.classList.remove('dimmed');
  586. } else {
  587. card.classList.remove('selected');
  588. card.classList.add('dimmed');
  589. }
  590. });
  591. // 收集所有支撑该目标点的实质点ID
  592. const supportingSubstances = new Set();
  593. relations.concrete_elements.forEach(item => supportingSubstances.add(item.substance_id));
  594. relations.concrete_concepts.forEach(item => supportingSubstances.add(item.substance_id));
  595. relations.abstract_concepts.forEach(item => supportingSubstances.add(item.substance_id));
  596. // 高亮支撑的实质点,其他变暗
  597. document.querySelectorAll('.substance-card').forEach(card => {
  598. const cardId = card.getAttribute('data-id');
  599. if (supportingSubstances.has(cardId)) {
  600. card.classList.add('highlighted');
  601. card.classList.remove('dimmed');
  602. } else {
  603. card.classList.add('dimmed');
  604. card.classList.remove('highlighted');
  605. }
  606. });
  607. // 绘制反向支撑关系
  608. drawReverseSupportRelationships(targetId, relations);
  609. }
  610. // 绘制支撑关系(实质点 → 目标点)
  611. function drawSupportRelationships(substanceId, relations) {
  612. const canvas = document.getElementById('support-canvas');
  613. if (!canvas) return;
  614. // 清空并移除占位文本
  615. const placeholder = canvas.querySelector('.placeholder-text');
  616. if (placeholder) {
  617. placeholder.style.display = 'none';
  618. }
  619. // 创建可视化内容
  620. let html = '<div class="support-flow">\n';
  621. html += '<div class="flow-title">支撑关系</div>\n';
  622. // 显示来源实质点
  623. const substanceData = supportRelationships.substance_to_target[substanceId];
  624. html += '<div class="flow-source">\n';
  625. html += '<div class="flow-node substance-flow-node">\n';
  626. html += `<div class="flow-node-label">${substanceData.type || '实质点'}</div>\n`;
  627. html += `<div class="flow-node-name">${substanceData.name || substanceId}</div>\n`;
  628. html += '</div>\n';
  629. html += '</div>\n';
  630. // 显示支撑的目标点(分类)
  631. html += '<div class="flow-targets">\n';
  632. if (relations.inspiration && relations.inspiration.length > 0) {
  633. html += '<div class="flow-target-group">\n';
  634. html += '<div class="flow-target-label">灵感点</div>\n';
  635. relations.inspiration.forEach(rel => {
  636. html += `<div class="flow-target-item inspiration-flow">\n`;
  637. html += `<div class="flow-target-text">${rel.point}</div>\n`;
  638. // 兼容新结构(支撑理由)和旧结构(相似度分数)
  639. if (rel.support_reason !== undefined) {
  640. html += `<div class="flow-target-score">支撑</div>\n`;
  641. } else if (rel.avg_score !== undefined) {
  642. const score = (rel.avg_score * 100).toFixed(0);
  643. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  644. }
  645. html += '</div>\n';
  646. });
  647. html += '</div>\n';
  648. }
  649. if (relations.purpose && relations.purpose.length > 0) {
  650. html += '<div class="flow-target-group">\n';
  651. html += '<div class="flow-target-label">目的点</div>\n';
  652. relations.purpose.forEach(rel => {
  653. html += `<div class="flow-target-item purpose-flow">\n`;
  654. html += `<div class="flow-target-text">${rel.point}</div>\n`;
  655. // 兼容新结构(支撑理由)和旧结构(相似度分数)
  656. if (rel.support_reason !== undefined) {
  657. html += `<div class="flow-target-score">支撑</div>\n`;
  658. } else if (rel.avg_score !== undefined) {
  659. const score = (rel.avg_score * 100).toFixed(0);
  660. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  661. }
  662. html += '</div>\n';
  663. });
  664. html += '</div>\n';
  665. }
  666. if (relations.keypoint && relations.keypoint.length > 0) {
  667. html += '<div class="flow-target-group">\n';
  668. html += '<div class="flow-target-label">关键点</div>\n';
  669. relations.keypoint.forEach(rel => {
  670. html += `<div class="flow-target-item keypoint-flow">\n`;
  671. html += `<div class="flow-target-text">${rel.point}</div>\n`;
  672. // 兼容新结构(支撑理由)和旧结构(相似度分数)
  673. if (rel.support_reason !== undefined) {
  674. html += `<div class="flow-target-score">支撑</div>\n`;
  675. } else if (rel.avg_score !== undefined) {
  676. const score = (rel.avg_score * 100).toFixed(0);
  677. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  678. }
  679. html += '</div>\n';
  680. });
  681. html += '</div>\n';
  682. }
  683. html += '</div>\n';
  684. html += '</div>\n';
  685. // 插入到画布
  686. canvas.insertAdjacentHTML('beforeend', html);
  687. }
  688. // 绘制反向支撑关系(目标点 ← 实质点)
  689. function drawReverseSupportRelationships(targetId, relations) {
  690. const canvas = document.getElementById('support-canvas');
  691. if (!canvas) return;
  692. // 清空并移除占位文本
  693. const placeholder = canvas.querySelector('.placeholder-text');
  694. if (placeholder) {
  695. placeholder.style.display = 'none';
  696. }
  697. // 创建可视化内容
  698. let html = '<div class="support-flow reverse">\n';
  699. html += '<div class="flow-title">被以下实质点支撑</div>\n';
  700. // 显示目标点
  701. html += '<div class="flow-source">\n';
  702. html += '<div class="flow-node target-flow-node">\n';
  703. html += `<div class="flow-node-label">目标点</div>\n`;
  704. html += `<div class="flow-node-name">${targetId}</div>\n`;
  705. html += '</div>\n';
  706. html += '</div>\n';
  707. // 显示支撑的实质点(分类)
  708. html += '<div class="flow-targets">\n';
  709. if (relations.concrete_elements && relations.concrete_elements.length > 0) {
  710. html += '<div class="flow-target-group">\n';
  711. html += '<div class="flow-target-label">具体元素</div>\n';
  712. relations.concrete_elements.forEach(item => {
  713. const score = (item.score * 100).toFixed(0);
  714. html += `<div class="flow-target-item concrete-element-flow">\n`;
  715. html += `<div class="flow-target-text">${item.name}</div>\n`;
  716. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  717. html += '</div>\n';
  718. });
  719. html += '</div>\n';
  720. }
  721. if (relations.concrete_concepts && relations.concrete_concepts.length > 0) {
  722. html += '<div class="flow-target-group">\n';
  723. html += '<div class="flow-target-label">具体概念</div>\n';
  724. relations.concrete_concepts.forEach(item => {
  725. const score = (item.score * 100).toFixed(0);
  726. html += `<div class="flow-target-item concrete-concept-flow">\n`;
  727. html += `<div class="flow-target-text">${item.name}</div>\n`;
  728. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  729. html += '</div>\n';
  730. });
  731. html += '</div>\n';
  732. }
  733. if (relations.abstract_concepts && relations.abstract_concepts.length > 0) {
  734. html += '<div class="flow-target-group">\n';
  735. html += '<div class="flow-target-label">抽象概念</div>\n';
  736. relations.abstract_concepts.forEach(item => {
  737. const score = (item.score * 100).toFixed(0);
  738. html += `<div class="flow-target-item abstract-concept-flow">\n`;
  739. html += `<div class="flow-target-text">${item.name}</div>\n`;
  740. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  741. html += '</div>\n';
  742. });
  743. html += '</div>\n';
  744. }
  745. html += '</div>\n';
  746. html += '</div>\n';
  747. // 插入到画布
  748. canvas.insertAdjacentHTML('beforeend', html);
  749. }
  750. // 清空支撑关系画布
  751. function clearSupportCanvas() {
  752. const canvas = document.getElementById('support-canvas');
  753. if (!canvas) return;
  754. // 移除所有flow相关元素
  755. const flows = canvas.querySelectorAll('.support-flow');
  756. flows.forEach(flow => flow.remove());
  757. // 重新显示占位文本
  758. const placeholder = canvas.querySelector('.placeholder-text');
  759. if (placeholder) {
  760. placeholder.style.display = 'flex';
  761. }
  762. }
  763. // ===== Tab5 实质与形式双向支撑关系图功能 =====
  764. let selectedLeftTargetType = null; // 左侧选题点类型:inspiration/keypoint/purpose
  765. let selectedLeftTargetIdx = null; // 左侧选题点索引
  766. let selectedTab5SubstanceId = null; // 选中的实质点ID (Tab5专用)
  767. let selectedFormId = null; // 选中的形式点ID
  768. let selectedRightTargetType = null; // 右侧选题点类型:inspiration/keypoint/purpose
  769. let selectedRightTargetIdx = null; // 右侧选题点索引
  770. // 选择左侧选题点(来自实质点支撑关系)
  771. function selectLeftTarget(targetType, targetIdx) {
  772. if (typeof tab5Relationships === 'undefined') {
  773. console.error('tab5Relationships not defined');
  774. return;
  775. }
  776. // 如果点击已选中的目标点,重置显示所有元素
  777. if (selectedLeftTargetType === targetType && selectedLeftTargetIdx === targetIdx) {
  778. resetTab5Selection();
  779. return;
  780. }
  781. // 重置其他选择
  782. resetTab5Selection();
  783. selectedLeftTargetType = targetType;
  784. selectedLeftTargetIdx = targetIdx;
  785. // 高亮选中的目标点
  786. document.querySelectorAll('.tab5-left-targets .target-card').forEach(card => {
  787. const dataType = card.getAttribute('data-type');
  788. const dataId = card.getAttribute('data-id');
  789. const fullId = `${targetType}-${targetIdx}`;
  790. if (dataId === fullId) {
  791. card.classList.add('selected');
  792. card.classList.remove('dimmed', 'hidden');
  793. } else {
  794. card.classList.remove('selected');
  795. card.classList.add('hidden');
  796. card.classList.remove('dimmed');
  797. }
  798. });
  799. // 形式点全部隐藏
  800. document.querySelectorAll('.tab5-forms .form-card').forEach(card => {
  801. card.classList.add('hidden');
  802. card.classList.remove('selected', 'highlighted', 'dimmed');
  803. });
  804. // 右侧选题点全部隐藏
  805. document.querySelectorAll('.tab5-right-targets .target-card').forEach(card => {
  806. card.classList.add('hidden');
  807. card.classList.remove('selected', 'highlighted', 'dimmed');
  808. });
  809. // 获取反向关系:哪些实质点支撑这个目标点(直接使用完整的targetIdx)
  810. const targetId = `${targetType}-${targetIdx}`;
  811. const supportingSubstances = tab5Relationships.target_from_substance[targetId];
  812. if (supportingSubstances && supportingSubstances.length > 0) {
  813. // 高亮支撑的实质点
  814. const substanceIds = new Set(supportingSubstances.map(s => s.substance_id));
  815. document.querySelectorAll('.tab5-substances .substance-card').forEach(card => {
  816. const cardId = card.getAttribute('data-id');
  817. if (substanceIds.has(cardId)) {
  818. card.classList.add('highlighted');
  819. card.classList.remove('dimmed', 'hidden');
  820. } else {
  821. card.classList.remove('highlighted');
  822. card.classList.add('hidden');
  823. card.classList.remove('dimmed');
  824. }
  825. });
  826. }
  827. // 绘制连线(只连接当前显示的卡片)
  828. setTimeout(() => {
  829. drawFormAllConnections();
  830. }, 50);
  831. }
  832. // 选择实质点
  833. function selectSubstance(substanceId) {
  834. if (typeof tab5Relationships === 'undefined') {
  835. console.error('tab5Relationships not defined');
  836. return;
  837. }
  838. // 如果点击已选中的实质点,重置显示所有元素
  839. if (selectedTab5SubstanceId === substanceId) {
  840. resetTab5Selection();
  841. return;
  842. }
  843. // 重置其他选择
  844. resetTab5Selection();
  845. selectedTab5SubstanceId = substanceId;
  846. // 高亮选中的实质点
  847. document.querySelectorAll('.tab5-substances .substance-card').forEach(card => {
  848. const cardId = card.getAttribute('data-id');
  849. if (cardId === substanceId) {
  850. card.classList.add('selected');
  851. card.classList.remove('dimmed', 'hidden');
  852. } else {
  853. card.classList.remove('selected');
  854. card.classList.add('hidden');
  855. card.classList.remove('dimmed');
  856. }
  857. });
  858. // 形式点全部隐藏(先全部隐藏,后面再显示有关联的)
  859. document.querySelectorAll('.tab5-forms .form-card').forEach(card => {
  860. card.classList.add('hidden');
  861. card.classList.remove('selected', 'highlighted', 'dimmed');
  862. });
  863. // 右侧选题点全部隐藏
  864. document.querySelectorAll('.tab5-right-targets .target-card').forEach(card => {
  865. card.classList.add('hidden');
  866. card.classList.remove('selected', 'highlighted', 'dimmed');
  867. });
  868. // 获取这个实质点支撑的选题点
  869. const relations = tab5Relationships.substance_to_target[substanceId];
  870. if (relations) {
  871. const leftTargets = new Set();
  872. relations.inspiration.forEach(rel => leftTargets.add(rel.target_id));
  873. relations.purpose.forEach(rel => leftTargets.add(rel.target_id));
  874. relations.keypoint.forEach(rel => leftTargets.add(rel.target_id));
  875. // 高亮左侧相关选题点
  876. document.querySelectorAll('.tab5-left-targets .target-card').forEach(card => {
  877. const cardId = card.getAttribute('data-id');
  878. if (leftTargets.has(cardId)) {
  879. card.classList.add('highlighted');
  880. card.classList.remove('dimmed', 'hidden');
  881. } else {
  882. card.classList.remove('highlighted');
  883. card.classList.add('hidden');
  884. card.classList.remove('dimmed');
  885. }
  886. });
  887. }
  888. // 高亮支撑这个实质点的形式点(反向关系)
  889. const supportingForms = tab5Relationships.substance_from_form[substanceId];
  890. if (supportingForms && supportingForms.length > 0) {
  891. const formIds = new Set(supportingForms.map(f => f.form_id));
  892. document.querySelectorAll('.tab5-forms .form-card').forEach(card => {
  893. const cardId = card.getAttribute('data-id');
  894. if (formIds.has(cardId)) {
  895. card.classList.add('highlighted');
  896. card.classList.remove('dimmed', 'hidden');
  897. }
  898. // 注意:这里不对其他形式点再次隐藏,因为已经在前面全部隐藏了
  899. });
  900. }
  901. // 绘制连线(只连接当前显示的卡片)
  902. setTimeout(() => {
  903. drawFormAllConnections();
  904. }, 50);
  905. }
  906. // 选择形式点
  907. function selectForm(formId) {
  908. if (typeof tab5Relationships === 'undefined') {
  909. console.error('tab5Relationships not defined');
  910. return;
  911. }
  912. // 如果点击已选中的形式点,重置显示所有元素
  913. if (selectedFormId === formId) {
  914. resetTab5Selection();
  915. return;
  916. }
  917. // 重置其他选择
  918. resetTab5Selection();
  919. selectedFormId = formId;
  920. // 高亮选中的形式点
  921. document.querySelectorAll('.tab5-forms .form-card').forEach(card => {
  922. const cardId = card.getAttribute('data-id');
  923. if (cardId === formId) {
  924. card.classList.add('selected');
  925. card.classList.remove('dimmed', 'hidden');
  926. } else {
  927. card.classList.remove('selected');
  928. card.classList.add('hidden');
  929. card.classList.remove('dimmed');
  930. }
  931. });
  932. // 实质点全部隐藏(先全部隐藏,后面再显示有关联的)
  933. document.querySelectorAll('.tab5-substances .substance-card').forEach(card => {
  934. card.classList.add('hidden');
  935. card.classList.remove('selected', 'highlighted', 'dimmed');
  936. });
  937. // 左侧选题点全部隐藏
  938. document.querySelectorAll('.tab5-left-targets .target-card').forEach(card => {
  939. card.classList.add('hidden');
  940. card.classList.remove('selected', 'highlighted', 'dimmed');
  941. });
  942. // 获取这个形式点支撑的选题点
  943. const relations = tab5Relationships.form_to_target[formId];
  944. if (relations) {
  945. const rightTargets = new Set();
  946. relations.inspiration.forEach(rel => rightTargets.add(rel.target_id));
  947. relations.purpose.forEach(rel => rightTargets.add(rel.target_id));
  948. relations.keypoint.forEach(rel => rightTargets.add(rel.target_id));
  949. // 高亮右侧相关选题点
  950. document.querySelectorAll('.tab5-right-targets .target-card').forEach(card => {
  951. const cardId = card.getAttribute('data-id');
  952. if (rightTargets.has(cardId)) {
  953. card.classList.add('highlighted');
  954. card.classList.remove('dimmed', 'hidden');
  955. } else {
  956. card.classList.remove('highlighted');
  957. card.classList.add('hidden');
  958. card.classList.remove('dimmed');
  959. }
  960. });
  961. }
  962. // 高亮这个形式点支撑的实质点
  963. const supportedSubstances = tab5Relationships.form_to_substance[formId];
  964. if (supportedSubstances && supportedSubstances.length > 0) {
  965. const substanceIds = new Set(supportedSubstances.map(s => s.substance_id));
  966. document.querySelectorAll('.tab5-substances .substance-card').forEach(card => {
  967. const cardId = card.getAttribute('data-id');
  968. if (substanceIds.has(cardId)) {
  969. card.classList.add('highlighted');
  970. card.classList.remove('dimmed', 'hidden');
  971. }
  972. // 注意:这里不对其他实质点再次隐藏,因为已经在前面全部隐藏了
  973. });
  974. }
  975. // 绘制连线(只连接当前显示的卡片)
  976. setTimeout(() => {
  977. drawFormAllConnections();
  978. }, 50);
  979. }
  980. // 选择右侧选题点(来自形式点支撑关系)
  981. function selectRightTarget(targetType, targetIdx) {
  982. if (typeof tab5Relationships === 'undefined') {
  983. console.error('tab5Relationships not defined');
  984. return;
  985. }
  986. // 如果点击已选中的目标点,重置显示所有元素
  987. if (selectedRightTargetType === targetType && selectedRightTargetIdx === targetIdx) {
  988. resetTab5Selection();
  989. return;
  990. }
  991. // 重置其他选择
  992. resetTab5Selection();
  993. selectedRightTargetType = targetType;
  994. selectedRightTargetIdx = targetIdx;
  995. // 高亮选中的目标点
  996. document.querySelectorAll('.tab5-right-targets .target-card').forEach(card => {
  997. const dataType = card.getAttribute('data-type');
  998. const dataId = card.getAttribute('data-id');
  999. const fullId = `${targetType}-${targetIdx}`;
  1000. if (dataId === fullId) {
  1001. card.classList.add('selected');
  1002. card.classList.remove('dimmed', 'hidden');
  1003. } else {
  1004. card.classList.remove('selected');
  1005. card.classList.add('hidden');
  1006. card.classList.remove('dimmed');
  1007. }
  1008. });
  1009. // 实质点全部隐藏
  1010. document.querySelectorAll('.tab5-substances .substance-card').forEach(card => {
  1011. card.classList.add('hidden');
  1012. card.classList.remove('selected', 'highlighted', 'dimmed');
  1013. });
  1014. // 左侧选题点全部隐藏
  1015. document.querySelectorAll('.tab5-left-targets .target-card').forEach(card => {
  1016. card.classList.add('hidden');
  1017. card.classList.remove('selected', 'highlighted', 'dimmed');
  1018. });
  1019. // 获取反向关系:哪些形式点支撑这个目标点(直接使用完整的targetIdx)
  1020. const targetId = `${targetType}-${targetIdx}`;
  1021. const supportingForms = tab5Relationships.target_from_form[targetId];
  1022. if (supportingForms && supportingForms.length > 0) {
  1023. // 高亮支撑的形式点
  1024. const formIds = new Set(supportingForms.map(f => f.form_id));
  1025. document.querySelectorAll('.tab5-forms .form-card').forEach(card => {
  1026. const cardId = card.getAttribute('data-id');
  1027. if (formIds.has(cardId)) {
  1028. card.classList.add('highlighted');
  1029. card.classList.remove('dimmed', 'hidden');
  1030. } else {
  1031. card.classList.remove('highlighted');
  1032. card.classList.add('hidden');
  1033. card.classList.remove('dimmed');
  1034. }
  1035. });
  1036. }
  1037. // 绘制连线(只连接当前显示的卡片)
  1038. setTimeout(() => {
  1039. drawFormAllConnections();
  1040. }, 50);
  1041. }
  1042. // 重置Tab5所有选择
  1043. function resetTab5Selection() {
  1044. selectedLeftTargetType = null;
  1045. selectedLeftTargetIdx = null;
  1046. selectedSubstanceId = null;
  1047. selectedFormId = null;
  1048. selectedRightTargetType = null;
  1049. selectedRightTargetIdx = null;
  1050. // 清除所有高亮、选中和隐藏状态
  1051. document.querySelectorAll('#tab5 .target-card, #tab5 .substance-card, #tab5 .form-card').forEach(card => {
  1052. card.classList.remove('selected', 'dimmed', 'highlighted', 'hidden');
  1053. });
  1054. // 清除所有连线
  1055. const svg = document.getElementById('tab5-connection-svg');
  1056. if (svg) {
  1057. svg.innerHTML = '';
  1058. }
  1059. }
  1060. // 选择目标点(反向查看)
  1061. function selectFormTarget(targetType, targetId) {
  1062. if (typeof formSupportRelationships === 'undefined') {
  1063. console.error('formSupportRelationships not defined');
  1064. return;
  1065. }
  1066. let fullTargetId;
  1067. if (targetType === 'substance') {
  1068. fullTargetId = `substance-${targetId}`;
  1069. } else {
  1070. fullTargetId = `${targetType}-${targetId}`;
  1071. }
  1072. // 如果点击已选中的目标点,不做处理
  1073. if (selectedFormTargetId === fullTargetId) {
  1074. return;
  1075. }
  1076. selectedFormId = null;
  1077. selectedFormTargetId = fullTargetId;
  1078. // 清空画布
  1079. clearFormSupportCanvas();
  1080. // 获取反向关系数据
  1081. const relations = formSupportRelationships.target_to_form[fullTargetId];
  1082. if (!relations) {
  1083. console.error('No relations found for target:', fullTargetId);
  1084. return;
  1085. }
  1086. // 清除所有形式点的状态
  1087. document.querySelectorAll('.form-card').forEach(card => {
  1088. card.classList.remove('selected', 'dimmed', 'highlighted');
  1089. });
  1090. // 高亮选中的目标点,其他变暗
  1091. document.querySelectorAll('#tab5 .target-card').forEach(card => {
  1092. if (card.getAttribute('data-id') === fullTargetId) {
  1093. card.classList.add('selected');
  1094. card.classList.remove('dimmed');
  1095. } else {
  1096. card.classList.remove('selected');
  1097. card.classList.add('dimmed');
  1098. }
  1099. });
  1100. // 收集所有支撑该目标点的形式点ID
  1101. const supportingForms = new Set();
  1102. if (relations.concrete_element_forms) {
  1103. relations.concrete_element_forms.forEach(item => supportingForms.add(item.form_id));
  1104. }
  1105. if (relations.concrete_concept_forms) {
  1106. relations.concrete_concept_forms.forEach(item => supportingForms.add(item.form_id));
  1107. }
  1108. if (relations.overall_forms) {
  1109. relations.overall_forms.forEach(item => supportingForms.add(item.form_id));
  1110. }
  1111. if (relations.forms) {
  1112. relations.forms.forEach(item => supportingForms.add(item.form_id));
  1113. }
  1114. // 高亮支撑的形式点,其他变暗
  1115. document.querySelectorAll('.form-card').forEach(card => {
  1116. const cardId = card.getAttribute('data-id');
  1117. if (supportingForms.has(cardId)) {
  1118. card.classList.add('highlighted');
  1119. card.classList.remove('dimmed');
  1120. } else {
  1121. card.classList.add('dimmed');
  1122. card.classList.remove('highlighted');
  1123. }
  1124. });
  1125. // 绘制反向支撑关系
  1126. drawReverseFormSupportRelationships(fullTargetId, relations);
  1127. }
  1128. // 绘制形式点支撑关系(形式点 → 目标点)
  1129. function drawFormSupportRelationships(formId, relations) {
  1130. const canvas = document.getElementById('form-support-canvas');
  1131. if (!canvas) return;
  1132. // 清空并移除占位文本
  1133. const placeholder = canvas.querySelector('.placeholder-text');
  1134. if (placeholder) {
  1135. placeholder.style.display = 'none';
  1136. }
  1137. // 创建可视化内容
  1138. let html = '<div class="support-flow">\n';
  1139. html += '<div class="flow-title">支撑关系</div>\n';
  1140. // 显示来源形式点
  1141. html += '<div class="flow-source">\n';
  1142. html += '<div class="flow-node form-flow-node">\n';
  1143. html += `<div class="flow-node-label">${relations.type || '形式点'}</div>\n`;
  1144. html += `<div class="flow-node-name">${relations.name || formId}</div>\n`;
  1145. html += '</div>\n';
  1146. html += '</div>\n';
  1147. // 显示支撑的目标点(分类)
  1148. html += '<div class="flow-targets">\n';
  1149. if (relations.inspiration && relations.inspiration.length > 0) {
  1150. html += '<div class="flow-target-group">\n';
  1151. html += '<div class="flow-target-label">灵感点</div>\n';
  1152. relations.inspiration.forEach(rel => {
  1153. html += `<div class="flow-target-item inspiration-flow">\n`;
  1154. html += `<div class="flow-target-text">${rel.point}</div>\n`;
  1155. // 兼容新结构(支撑理由)和旧结构(相似度分数)
  1156. if (rel.support_reason !== undefined) {
  1157. html += `<div class="flow-target-score">支撑</div>\n`;
  1158. } else if (rel.avg_score !== undefined) {
  1159. const score = (rel.avg_score * 100).toFixed(0);
  1160. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  1161. }
  1162. html += '</div>\n';
  1163. });
  1164. html += '</div>\n';
  1165. }
  1166. if (relations.purpose && relations.purpose.length > 0) {
  1167. html += '<div class="flow-target-group">\n';
  1168. html += '<div class="flow-target-label">目的点</div>\n';
  1169. relations.purpose.forEach(rel => {
  1170. html += `<div class="flow-target-item purpose-flow">\n`;
  1171. html += `<div class="flow-target-text">${rel.point}</div>\n`;
  1172. // 兼容新结构(支撑理由)和旧结构(相似度分数)
  1173. if (rel.support_reason !== undefined) {
  1174. html += `<div class="flow-target-score">支撑</div>\n`;
  1175. } else if (rel.avg_score !== undefined) {
  1176. const score = (rel.avg_score * 100).toFixed(0);
  1177. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  1178. }
  1179. html += '</div>\n';
  1180. });
  1181. html += '</div>\n';
  1182. }
  1183. if (relations.keypoint && relations.keypoint.length > 0) {
  1184. html += '<div class="flow-target-group">\n';
  1185. html += '<div class="flow-target-label">关键点</div>\n';
  1186. relations.keypoint.forEach(rel => {
  1187. html += `<div class="flow-target-item keypoint-flow">\n`;
  1188. html += `<div class="flow-target-text">${rel.point}</div>\n`;
  1189. // 兼容新结构(支撑理由)和旧结构(相似度分数)
  1190. if (rel.support_reason !== undefined) {
  1191. html += `<div class="flow-target-score">支撑</div>\n`;
  1192. } else if (rel.avg_score !== undefined) {
  1193. const score = (rel.avg_score * 100).toFixed(0);
  1194. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  1195. }
  1196. html += '</div>\n';
  1197. });
  1198. html += '</div>\n';
  1199. }
  1200. if (relations.substance && relations.substance.length > 0) {
  1201. html += '<div class="flow-target-group">\n';
  1202. html += '<div class="flow-target-label">实质点</div>\n';
  1203. relations.substance.forEach(rel => {
  1204. const score = (rel.avg_score * 100).toFixed(0);
  1205. let flowClass = '';
  1206. if (rel.type === '具体元素') {
  1207. flowClass = 'concrete-element-flow';
  1208. } else if (rel.type === '具体概念') {
  1209. flowClass = 'concrete-concept-flow';
  1210. } else if (rel.type === '抽象概念') {
  1211. flowClass = 'abstract-concept-flow';
  1212. }
  1213. html += `<div class="flow-target-item ${flowClass}">\n`;
  1214. html += `<div class="flow-target-text">${rel.name} <span style="font-size:0.8em;color:#6c757d;">(${rel.type})</span></div>\n`;
  1215. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  1216. html += '</div>\n';
  1217. });
  1218. html += '</div>\n';
  1219. }
  1220. html += '</div>\n';
  1221. html += '</div>\n';
  1222. // 插入到画布
  1223. canvas.insertAdjacentHTML('beforeend', html);
  1224. }
  1225. // 绘制反向形式点支撑关系(目标点 ← 形式点)
  1226. function drawReverseFormSupportRelationships(targetId, relations) {
  1227. const canvas = document.getElementById('form-support-canvas');
  1228. if (!canvas) return;
  1229. // 清空并移除占位文本
  1230. const placeholder = canvas.querySelector('.placeholder-text');
  1231. if (placeholder) {
  1232. placeholder.style.display = 'none';
  1233. }
  1234. // 创建可视化内容
  1235. let html = '<div class="support-flow reverse">\n';
  1236. html += '<div class="flow-title">被以下形式点支撑</div>\n';
  1237. // 显示目标点
  1238. html += '<div class="flow-source">\n';
  1239. html += '<div class="flow-node target-flow-node">\n';
  1240. html += `<div class="flow-node-label">目标点</div>\n`;
  1241. html += `<div class="flow-node-name">${targetId}</div>\n`;
  1242. html += '</div>\n';
  1243. html += '</div>\n';
  1244. // 显示支撑的形式点(分类)
  1245. html += '<div class="flow-targets">\n';
  1246. if (relations.concrete_element_forms && relations.concrete_element_forms.length > 0) {
  1247. html += '<div class="flow-target-group">\n';
  1248. html += '<div class="flow-target-label">具体元素形式</div>\n';
  1249. relations.concrete_element_forms.forEach(item => {
  1250. const score = (item.score * 100).toFixed(0);
  1251. html += `<div class="flow-target-item concrete-element-form-flow">\n`;
  1252. html += `<div class="flow-target-text">${item.name}</div>\n`;
  1253. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  1254. html += '</div>\n';
  1255. });
  1256. html += '</div>\n';
  1257. }
  1258. if (relations.concrete_concept_forms && relations.concrete_concept_forms.length > 0) {
  1259. html += '<div class="flow-target-group">\n';
  1260. html += '<div class="flow-target-label">具体概念形式</div>\n';
  1261. relations.concrete_concept_forms.forEach(item => {
  1262. const score = (item.score * 100).toFixed(0);
  1263. html += `<div class="flow-target-item concrete-concept-form-flow">\n`;
  1264. html += `<div class="flow-target-text">${item.name}</div>\n`;
  1265. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  1266. html += '</div>\n';
  1267. });
  1268. html += '</div>\n';
  1269. }
  1270. if (relations.overall_forms && relations.overall_forms.length > 0) {
  1271. html += '<div class="flow-target-group">\n';
  1272. html += '<div class="flow-target-label">整体形式</div>\n';
  1273. relations.overall_forms.forEach(item => {
  1274. const score = (item.score * 100).toFixed(0);
  1275. html += `<div class="flow-target-item overall-form-flow">\n`;
  1276. html += `<div class="flow-target-text">${item.name}</div>\n`;
  1277. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  1278. html += '</div>\n';
  1279. });
  1280. html += '</div>\n';
  1281. }
  1282. if (relations.forms && relations.forms.length > 0) {
  1283. html += '<div class="flow-target-group">\n';
  1284. html += '<div class="flow-target-label">形式点</div>\n';
  1285. relations.forms.forEach(item => {
  1286. const score = (item.score * 100).toFixed(0);
  1287. html += `<div class="flow-target-item form-flow">\n`;
  1288. html += `<div class="flow-target-text">${item.name} <span style="font-size:0.8em;color:#6c757d;">(${item.type})</span></div>\n`;
  1289. html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
  1290. html += '</div>\n';
  1291. });
  1292. html += '</div>\n';
  1293. }
  1294. html += '</div>\n';
  1295. html += '</div>\n';
  1296. // 插入到画布
  1297. canvas.insertAdjacentHTML('beforeend', html);
  1298. }
  1299. // 清空形式点支撑关系画布
  1300. function clearFormSupportCanvas() {
  1301. const canvas = document.getElementById('form-support-canvas');
  1302. if (!canvas) return;
  1303. // 移除所有flow相关元素
  1304. const flows = canvas.querySelectorAll('.support-flow');
  1305. flows.forEach(flow => flow.remove());
  1306. // 重新显示占位文本
  1307. const placeholder = canvas.querySelector('.placeholder-text');
  1308. if (placeholder) {
  1309. placeholder.style.display = 'flex';
  1310. }
  1311. }
  1312. // ===== Tab5 连线绘制功能 =====
  1313. // 绘制Tab5所有连线
  1314. function drawFormAllConnections() {
  1315. if (typeof tab5Relationships === 'undefined') {
  1316. console.log('tab5Relationships not defined, skipping connection drawing');
  1317. return;
  1318. }
  1319. const svg = document.getElementById('tab5-connection-svg');
  1320. if (!svg) {
  1321. console.log('SVG container not found');
  1322. return;
  1323. }
  1324. // 清空现有连线
  1325. svg.innerHTML = '';
  1326. // 1. 绘制左侧选题点到实质点的连线(反向关系)
  1327. drawLeftTargetToSubstanceConnections(svg);
  1328. // 2. 绘制实质点到形式点的连线(支撑关系)
  1329. drawSubstanceToFormConnections(svg);
  1330. // 3. 绘制形式点到右侧选题点的连线
  1331. drawFormToRightTargetConnections(svg);
  1332. }
  1333. // 绘制左侧选题点到实质点的连线
  1334. function drawLeftTargetToSubstanceConnections(svg) {
  1335. const targetFromSubstance = tab5Relationships.target_from_substance;
  1336. Object.keys(targetFromSubstance).forEach(targetId => {
  1337. const substances = targetFromSubstance[targetId];
  1338. substances.forEach(substanceData => {
  1339. const sourceCard = document.querySelector(`.tab5-left-targets .target-card[data-id="${targetId}"]`);
  1340. const targetCard = document.querySelector(`.tab5-substances .substance-card[data-id="${substanceData.substance_id}"]`);
  1341. if (sourceCard && targetCard && !sourceCard.classList.contains('hidden') && !targetCard.classList.contains('hidden')) {
  1342. drawTab5Connection(
  1343. svg,
  1344. sourceCard,
  1345. targetCard,
  1346. 'left-target-substance',
  1347. targetId,
  1348. substanceData.substance_id,
  1349. {
  1350. score: substanceData.score,
  1351. substanceName: substanceData.name,
  1352. type: substanceData.type
  1353. }
  1354. );
  1355. }
  1356. });
  1357. });
  1358. }
  1359. // 绘制实质点到形式点的连线
  1360. function drawSubstanceToFormConnections(svg) {
  1361. const formToSubstance = tab5Relationships.form_to_substance;
  1362. Object.keys(formToSubstance).forEach(formId => {
  1363. const substances = formToSubstance[formId];
  1364. substances.forEach(substanceData => {
  1365. const sourceCard = document.querySelector(`.tab5-substances .substance-card[data-id="${substanceData.substance_id}"]`);
  1366. const targetCard = document.querySelector(`.tab5-forms .form-card[data-id="${formId}"]`);
  1367. if (sourceCard && targetCard && !sourceCard.classList.contains('hidden') && !targetCard.classList.contains('hidden')) {
  1368. drawTab5Connection(
  1369. svg,
  1370. sourceCard,
  1371. targetCard,
  1372. 'substance-form',
  1373. substanceData.substance_id,
  1374. formId,
  1375. {
  1376. substanceName: substanceData.name,
  1377. formName: tab5Relationships.form_to_target[formId]?.name || ''
  1378. }
  1379. );
  1380. }
  1381. });
  1382. });
  1383. }
  1384. // 绘制形式点到右侧选题点的连线
  1385. function drawFormToRightTargetConnections(svg) {
  1386. const formToTarget = tab5Relationships.form_to_target;
  1387. Object.keys(formToTarget).forEach(formId => {
  1388. const relations = formToTarget[formId];
  1389. // 绘制到灵感点的连线
  1390. relations.inspiration.forEach(rel => {
  1391. const sourceCard = document.querySelector(`.tab5-forms .form-card[data-id="${formId}"]`);
  1392. const targetCard = document.querySelector(`.tab5-right-targets .target-card[data-id="${rel.target_id}"]`);
  1393. if (sourceCard && targetCard && !sourceCard.classList.contains('hidden') && !targetCard.classList.contains('hidden')) {
  1394. // 兼容新结构(支撑)和旧结构(相似度分数)
  1395. const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
  1396. drawTab5Connection(
  1397. svg,
  1398. sourceCard,
  1399. targetCard,
  1400. 'form-inspiration',
  1401. formId,
  1402. rel.target_id,
  1403. {
  1404. score: score,
  1405. point: rel.point,
  1406. formName: relations.name
  1407. }
  1408. );
  1409. }
  1410. });
  1411. // 绘制到目的点的连线
  1412. relations.purpose.forEach(rel => {
  1413. const sourceCard = document.querySelector(`.tab5-forms .form-card[data-id="${formId}"]`);
  1414. const targetCard = document.querySelector(`.tab5-right-targets .target-card[data-id="${rel.target_id}"]`);
  1415. if (sourceCard && targetCard && !sourceCard.classList.contains('hidden') && !targetCard.classList.contains('hidden')) {
  1416. // 兼容新结构(支撑)和旧结构(相似度分数)
  1417. const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
  1418. drawTab5Connection(
  1419. svg,
  1420. sourceCard,
  1421. targetCard,
  1422. 'form-purpose',
  1423. formId,
  1424. rel.target_id,
  1425. {
  1426. score: score,
  1427. point: rel.point,
  1428. formName: relations.name
  1429. }
  1430. );
  1431. }
  1432. });
  1433. // 绘制到关键点的连线
  1434. relations.keypoint.forEach(rel => {
  1435. const sourceCard = document.querySelector(`.tab5-forms .form-card[data-id="${formId}"]`);
  1436. const targetCard = document.querySelector(`.tab5-right-targets .target-card[data-id="${rel.target_id}"]`);
  1437. if (sourceCard && targetCard && !sourceCard.classList.contains('hidden') && !targetCard.classList.contains('hidden')) {
  1438. // 兼容新结构(支撑)和旧结构(相似度分数)
  1439. const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
  1440. drawTab5Connection(
  1441. svg,
  1442. sourceCard,
  1443. targetCard,
  1444. 'form-keypoint',
  1445. formId,
  1446. rel.target_id,
  1447. {
  1448. score: score,
  1449. point: rel.point,
  1450. formName: relations.name
  1451. }
  1452. );
  1453. }
  1454. });
  1455. });
  1456. }
  1457. // 绘制单条Tab5连线
  1458. function drawTab5Connection(svg, sourceCard, targetCard, connectionType, sourceId, targetId, metadata) {
  1459. const svgContainer = svg.parentElement;
  1460. const containerRect = svgContainer.getBoundingClientRect();
  1461. const sourceRect = sourceCard.getBoundingClientRect();
  1462. const targetRect = targetCard.getBoundingClientRect();
  1463. // 计算连线起点和终点
  1464. let x1, y1, x2, y2;
  1465. if (connectionType === 'left-target-substance') {
  1466. // 左侧选题点到实质点:从右边连到左边
  1467. x1 = sourceRect.right - containerRect.left;
  1468. y1 = sourceRect.top + sourceRect.height / 2 - containerRect.top;
  1469. x2 = targetRect.left - containerRect.left;
  1470. y2 = targetRect.top + targetRect.height / 2 - containerRect.top;
  1471. } else if (connectionType === 'substance-form') {
  1472. // 实质点到形式点:从右边连到左边
  1473. x1 = sourceRect.right - containerRect.left;
  1474. y1 = sourceRect.top + sourceRect.height / 2 - containerRect.top;
  1475. x2 = targetRect.left - containerRect.left;
  1476. y2 = targetRect.top + targetRect.height / 2 - containerRect.top;
  1477. } else {
  1478. // 形式点到右侧选题点:从右边连到左边
  1479. x1 = sourceRect.right - containerRect.left;
  1480. y1 = sourceRect.top + sourceRect.height / 2 - containerRect.top;
  1481. x2 = targetRect.left - containerRect.left;
  1482. y2 = targetRect.top + targetRect.height / 2 - containerRect.top;
  1483. }
  1484. // 创建贝塞尔曲线路径
  1485. const midX = (x1 + x2) / 2;
  1486. const path = `M ${x1} ${y1} Q ${midX} ${y1}, ${midX} ${(y1 + y2) / 2} T ${x2} ${y2}`;
  1487. // 创建path元素
  1488. const pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  1489. pathElement.setAttribute('d', path);
  1490. pathElement.setAttribute('class', `tab5-connection-line ${connectionType}`);
  1491. pathElement.setAttribute('fill', 'none');
  1492. pathElement.setAttribute('stroke-width', '2');
  1493. pathElement.setAttribute('data-source-id', sourceId);
  1494. pathElement.setAttribute('data-target-id', targetId);
  1495. pathElement.setAttribute('data-connection-type', connectionType);
  1496. // 保存元数据以供点击时使用
  1497. pathElement.setAttribute('data-metadata', JSON.stringify(metadata));
  1498. // 添加点击事件
  1499. pathElement.style.cursor = 'pointer';
  1500. pathElement.addEventListener('click', function(e) {
  1501. e.stopPropagation();
  1502. showConnectionDetail(connectionType, sourceId, targetId, metadata);
  1503. });
  1504. // 添加hover效果
  1505. pathElement.addEventListener('mouseenter', function() {
  1506. this.setAttribute('stroke-width', '4');
  1507. this.style.filter = 'drop-shadow(0 0 6px rgba(0,0,0,0.3))';
  1508. });
  1509. pathElement.addEventListener('mouseleave', function() {
  1510. this.setAttribute('stroke-width', '2');
  1511. this.style.filter = 'none';
  1512. });
  1513. svg.appendChild(pathElement);
  1514. }
  1515. // 显示连线详情模态框
  1516. function showConnectionDetail(connectionType, sourceId, targetId, metadata) {
  1517. // 创建模态框
  1518. const modal = document.createElement('div');
  1519. modal.className = 'connection-modal-backdrop';
  1520. modal.onclick = function(e) {
  1521. if (e.target === modal) {
  1522. document.body.removeChild(modal);
  1523. }
  1524. };
  1525. // 创建模态框内容
  1526. const modalContent = document.createElement('div');
  1527. modalContent.className = 'connection-modal-content';
  1528. modalContent.onclick = function(e) {
  1529. e.stopPropagation();
  1530. };
  1531. // 构建内容
  1532. let html = '<div class="modal-header">';
  1533. html += '<h3>连线关系详情</h3>';
  1534. html += '<button class="modal-close" onclick="this.closest(\'.connection-modal-backdrop\').remove()">×</button>';
  1535. html += '</div>';
  1536. html += '<div class="modal-body">';
  1537. // 根据连接类型显示不同内容
  1538. if (connectionType === 'left-target-substance') {
  1539. html += '<div class="connection-detail-section">';
  1540. html += '<div class="connection-label">连接类型:</div>';
  1541. html += '<div class="connection-value">选题点 → 实质点</div>';
  1542. html += '</div>';
  1543. html += '<div class="connection-detail-section">';
  1544. html += '<div class="connection-label">实质点:</div>';
  1545. html += '<div class="connection-value">' + (metadata.substanceName || sourceId) + '</div>';
  1546. html += '</div>';
  1547. html += '<div class="connection-detail-section">';
  1548. html += '<div class="connection-label">实质点类型:</div>';
  1549. html += '<div class="connection-value">' + (metadata.type || '-') + '</div>';
  1550. html += '</div>';
  1551. if (metadata.score !== undefined) {
  1552. const scorePercent = (metadata.score * 100).toFixed(1);
  1553. html += '<div class="connection-detail-section">';
  1554. html += '<div class="connection-label">相似度评分:</div>';
  1555. html += '<div class="connection-value score-value">' + scorePercent + '%</div>';
  1556. html += '</div>';
  1557. }
  1558. html += '<div class="connection-detail-section">';
  1559. html += '<div class="connection-label">说明:</div>';
  1560. html += '<div class="connection-value">该实质点支撑了这个选题点的内容,相似度越高表示关联性越强</div>';
  1561. html += '</div>';
  1562. } else if (connectionType === 'substance-form') {
  1563. html += '<div class="connection-detail-section">';
  1564. html += '<div class="connection-label">连接类型:</div>';
  1565. html += '<div class="connection-value">实质点 → 形式点</div>';
  1566. html += '</div>';
  1567. html += '<div class="connection-detail-section">';
  1568. html += '<div class="connection-label">实质点:</div>';
  1569. html += '<div class="connection-value">' + (metadata.substanceName || sourceId) + '</div>';
  1570. html += '</div>';
  1571. html += '<div class="connection-detail-section">';
  1572. html += '<div class="connection-label">形式点:</div>';
  1573. html += '<div class="connection-value">' + (metadata.formName || targetId) + '</div>';
  1574. html += '</div>';
  1575. html += '<div class="connection-detail-section">';
  1576. html += '<div class="connection-label">说明:</div>';
  1577. html += '<div class="connection-value">该形式点是对实质点的具体表现形式</div>';
  1578. html += '</div>';
  1579. } else if (connectionType.startsWith('form-')) {
  1580. const targetType = connectionType.split('-')[1]; // inspiration, purpose, keypoint
  1581. const targetTypeLabel = {
  1582. 'inspiration': '灵感点',
  1583. 'purpose': '目的点',
  1584. 'keypoint': '关键点'
  1585. }[targetType] || targetType;
  1586. html += '<div class="connection-detail-section">';
  1587. html += '<div class="connection-label">连接类型:</div>';
  1588. html += '<div class="connection-value">形式点 → ' + targetTypeLabel + '</div>';
  1589. html += '</div>';
  1590. html += '<div class="connection-detail-section">';
  1591. html += '<div class="connection-label">形式点:</div>';
  1592. html += '<div class="connection-value">' + (metadata.formName || sourceId) + '</div>';
  1593. html += '</div>';
  1594. if (metadata.point) {
  1595. html += '<div class="connection-detail-section">';
  1596. html += '<div class="connection-label">' + targetTypeLabel + ':</div>';
  1597. html += '<div class="connection-value">' + metadata.point + '</div>';
  1598. html += '</div>';
  1599. }
  1600. if (metadata.score !== undefined) {
  1601. const scorePercent = (metadata.score * 100).toFixed(1);
  1602. html += '<div class="connection-detail-section">';
  1603. html += '<div class="connection-label">相似度评分:</div>';
  1604. html += '<div class="connection-value score-value">' + scorePercent + '%</div>';
  1605. html += '</div>';
  1606. }
  1607. html += '<div class="connection-detail-section">';
  1608. html += '<div class="connection-label">说明:</div>';
  1609. html += '<div class="connection-value">该形式点支撑了这个选题点的呈现,相似度越高表示形式与选题的契合度越高</div>';
  1610. html += '</div>';
  1611. }
  1612. html += '</div>';
  1613. modalContent.innerHTML = html;
  1614. modal.appendChild(modalContent);
  1615. document.body.appendChild(modal);
  1616. }