index.html 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>热点新闻分析</title>
  7. <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js" integrity="sha512-BNaRQnYJYiPSqHHDb58B0yaPfCu+Wgds8Gp/gU33kqBtgNS4tSPHuGibyoeqMV/TJlSKda6FXzoEyYGjTe+vXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  8. <style>
  9. * {
  10. box-sizing: border-box;
  11. }
  12. body {
  13. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  14. margin: 0;
  15. padding: 16px;
  16. background: #fafafa;
  17. color: #333;
  18. line-height: 1.5;
  19. }
  20. .container {
  21. max-width: 600px;
  22. margin: 0 auto;
  23. background: white;
  24. border-radius: 12px;
  25. overflow: hidden;
  26. box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
  27. }
  28. .header {
  29. background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
  30. color: white;
  31. padding: 32px 24px;
  32. text-align: center;
  33. position: relative;
  34. }
  35. .save-buttons {
  36. position: absolute;
  37. top: 16px;
  38. right: 16px;
  39. display: flex;
  40. gap: 8px;
  41. }
  42. .save-btn {
  43. background: rgba(255, 255, 255, 0.2);
  44. border: 1px solid rgba(255, 255, 255, 0.3);
  45. color: white;
  46. padding: 8px 16px;
  47. border-radius: 6px;
  48. cursor: pointer;
  49. font-size: 13px;
  50. font-weight: 500;
  51. transition: all 0.2s ease;
  52. backdrop-filter: blur(10px);
  53. white-space: nowrap;
  54. }
  55. .save-btn:hover {
  56. background: rgba(255, 255, 255, 0.3);
  57. border-color: rgba(255, 255, 255, 0.5);
  58. transform: translateY(-1px);
  59. }
  60. .save-btn:active {
  61. transform: translateY(0);
  62. }
  63. .save-btn:disabled {
  64. opacity: 0.6;
  65. cursor: not-allowed;
  66. }
  67. .header-title {
  68. font-size: 22px;
  69. font-weight: 700;
  70. margin: 0 0 20px 0;
  71. }
  72. .header-info {
  73. display: grid;
  74. grid-template-columns: 1fr 1fr;
  75. gap: 16px;
  76. font-size: 14px;
  77. opacity: 0.95;
  78. }
  79. .info-item {
  80. text-align: center;
  81. }
  82. .info-label {
  83. display: block;
  84. font-size: 12px;
  85. opacity: 0.8;
  86. margin-bottom: 4px;
  87. }
  88. .info-value {
  89. font-weight: 600;
  90. font-size: 16px;
  91. }
  92. .content {
  93. padding: 24px;
  94. }
  95. .word-group {
  96. margin-bottom: 40px;
  97. }
  98. .word-group:first-child {
  99. margin-top: 0;
  100. }
  101. .word-header {
  102. display: flex;
  103. align-items: center;
  104. justify-content: space-between;
  105. margin-bottom: 20px;
  106. padding-bottom: 8px;
  107. border-bottom: 1px solid #f0f0f0;
  108. }
  109. .word-info {
  110. display: flex;
  111. align-items: center;
  112. gap: 12px;
  113. }
  114. .word-name {
  115. font-size: 17px;
  116. font-weight: 600;
  117. color: #1a1a1a;
  118. }
  119. .word-count {
  120. color: #666;
  121. font-size: 13px;
  122. font-weight: 500;
  123. }
  124. .word-count.hot {
  125. color: #dc2626;
  126. font-weight: 600;
  127. }
  128. .word-count.warm {
  129. color: #ea580c;
  130. font-weight: 600;
  131. }
  132. .word-index {
  133. color: #999;
  134. font-size: 12px;
  135. }
  136. .news-item {
  137. margin-bottom: 20px;
  138. padding: 16px 0;
  139. border-bottom: 1px solid #f5f5f5;
  140. position: relative;
  141. display: flex;
  142. gap: 12px;
  143. align-items: center;
  144. }
  145. .news-item:last-child {
  146. border-bottom: none;
  147. }
  148. .news-number {
  149. color: #999;
  150. font-size: 13px;
  151. font-weight: 600;
  152. min-width: 20px;
  153. text-align: center;
  154. flex-shrink: 0;
  155. background: #f8f9fa;
  156. border-radius: 50%;
  157. width: 24px;
  158. height: 24px;
  159. display: flex;
  160. align-items: center;
  161. justify-content: center;
  162. align-self: flex-start;
  163. margin-top: 8px;
  164. }
  165. .news-content {
  166. flex: 1;
  167. min-width: 0;
  168. }
  169. .news-header {
  170. display: flex;
  171. align-items: center;
  172. gap: 8px;
  173. margin-bottom: 8px;
  174. flex-wrap: wrap;
  175. }
  176. .source-name {
  177. color: #666;
  178. font-size: 12px;
  179. font-weight: 500;
  180. }
  181. .rank-num {
  182. color: #fff;
  183. background: #6b7280;
  184. font-size: 10px;
  185. font-weight: 700;
  186. padding: 2px 6px;
  187. border-radius: 10px;
  188. min-width: 18px;
  189. text-align: center;
  190. }
  191. .rank-num.top {
  192. background: #dc2626;
  193. }
  194. .rank-num.high {
  195. background: #ea580c;
  196. }
  197. .time-info {
  198. color: #999;
  199. font-size: 11px;
  200. }
  201. .count-info {
  202. color: #059669;
  203. font-size: 11px;
  204. font-weight: 500;
  205. }
  206. .news-title {
  207. font-size: 15px;
  208. line-height: 1.4;
  209. color: #1a1a1a;
  210. margin: 0;
  211. }
  212. .news-link {
  213. color: #2563eb;
  214. text-decoration: none;
  215. }
  216. .news-link:hover {
  217. text-decoration: underline;
  218. }
  219. .news-link:visited {
  220. color: #7c3aed;
  221. }
  222. .footer {
  223. margin-top: 32px;
  224. padding: 20px 24px;
  225. background: #f8f9fa;
  226. border-top: 1px solid #e5e7eb;
  227. text-align: center;
  228. }
  229. .footer-content {
  230. font-size: 13px;
  231. color: #6b7280;
  232. line-height: 1.6;
  233. }
  234. .footer-link {
  235. color: #4f46e5;
  236. text-decoration: none;
  237. font-weight: 500;
  238. transition: color 0.2s ease;
  239. }
  240. .footer-link:hover {
  241. color: #7c3aed;
  242. text-decoration: underline;
  243. }
  244. .project-name {
  245. font-weight: 600;
  246. color: #374151;
  247. }
  248. @media (max-width: 480px) {
  249. body {
  250. padding: 12px;
  251. }
  252. .header {
  253. padding: 24px 20px;
  254. }
  255. .content {
  256. padding: 20px;
  257. }
  258. .footer {
  259. padding: 16px 20px;
  260. }
  261. .header-info {
  262. grid-template-columns: 1fr;
  263. gap: 12px;
  264. }
  265. .news-header {
  266. gap: 6px;
  267. }
  268. .news-item {
  269. gap: 8px;
  270. }
  271. .news-number {
  272. width: 20px;
  273. height: 20px;
  274. font-size: 12px;
  275. }
  276. .save-buttons {
  277. position: static;
  278. margin-bottom: 16px;
  279. display: flex;
  280. gap: 8px;
  281. justify-content: center;
  282. flex-direction: column;
  283. width: 100%;
  284. }
  285. .save-btn {
  286. width: 100%;
  287. }
  288. }
  289. </style>
  290. </head>
  291. <body>
  292. <div class="container">
  293. <div class="header">
  294. <div class="save-buttons">
  295. <button class="save-btn" onclick="saveAsImage()">保存为图片</button>
  296. <button class="save-btn" onclick="saveAsMultipleImages()">分段保存</button>
  297. </div>
  298. <div class="header-title">热点新闻分析</div>
  299. <div class="header-info">
  300. <div class="info-item">
  301. <span class="info-label">报告类型</span>
  302. <span class="info-value">当日汇总</span>
  303. </div>
  304. <div class="info-item">
  305. <span class="info-label">新闻总数</span>
  306. <span class="info-value">387 条</span>
  307. </div>
  308. <div class="info-item">
  309. <span class="info-label">热点新闻</span>
  310. <span class="info-value">5 条</span>
  311. </div>
  312. <div class="info-item">
  313. <span class="info-label">生成时间</span>
  314. <span class="info-value">06-16 07:17</span>
  315. </div>
  316. </div>
  317. </div>
  318. <div class="content">
  319. <div class="word-group">
  320. <div class="word-header">
  321. <div class="word-info">
  322. <div class="word-name">ai 人工智能</div>
  323. <div class="word-count hot">3 条</div>
  324. </div>
  325. <div class="word-index">1/4</div>
  326. </div>
  327. <div class="news-item">
  328. <div class="news-number">1</div>
  329. <div class="news-content">
  330. <div class="news-header">
  331. <span class="source-name">财联社热门</span>
  332. <span class="rank-num high">7-8</span>
  333. <span class="time-info">00:23~07:17</span>
  334. <span class="count-info">15次</span>
  335. </div>
  336. <div class="news-title">
  337. <a href="https://www.cls.cn/detail/2057563" target="_blank" class="news-link">上市首日暴涨140% 军用无人机公司登陆纽交所 AI打造产品核心竞争力</a>
  338. </div>
  339. </div>
  340. </div>
  341. <div class="news-item">
  342. <div class="news-number">2</div>
  343. <div class="news-content">
  344. <div class="news-header">
  345. <span class="source-name">tieba</span>
  346. <span class="rank-num">18-19</span>
  347. <span class="time-info">00:23~07:17</span>
  348. <span class="count-info">15次</span>
  349. </div>
  350. <div class="news-title">
  351. <a href="https://tieba.baidu.com/hottopic/browse/hottopic?topic_id=28342819&topic_name=%E4%BC%8A%E6%9C%97%E7%96%91%E7%94%A8AI%E4%BC%AA%E9%80%A0%E4%BB%A5%E5%86%9BF35%E6%AE%8B%E9%AA%B8%E5%9B%BE" target="_blank" class="news-link">伊朗疑用AI伪造以军F35残骸图</a>
  352. </div>
  353. </div>
  354. </div>
  355. <div class="news-item">
  356. <div class="news-number">3</div>
  357. <div class="news-content">
  358. <div class="news-header">
  359. <span class="source-name">zhihu</span>
  360. <span class="rank-num top">5-13</span>
  361. <span class="time-info">00:23~07:17</span>
  362. <span class="count-info">15次</span>
  363. </div>
  364. <div class="news-title">
  365. <a href="https://www.zhihu.com/question/596907281" target="_blank" class="news-link">罗杰·彭罗斯说无论意识是什么,都绝对不是一种计算。意思是:任何 AI 都不可能产生意识?</a>
  366. </div>
  367. </div>
  368. </div>
  369. </div>
  370. <div class="word-group">
  371. <div class="word-header">
  372. <div class="word-info">
  373. <div class="word-name">DeepSeek 梁文锋</div>
  374. <div class="word-count">1 条</div>
  375. </div>
  376. <div class="word-index">2/4</div>
  377. </div>
  378. <div class="news-item">
  379. <div class="news-number">1</div>
  380. <div class="news-content">
  381. <div class="news-header">
  382. <span class="source-name">华尔街见闻</span>
  383. <span class="rank-num high">8-9</span>
  384. <span class="time-info">00:23~07:17</span>
  385. <span class="count-info">15次</span>
  386. </div>
  387. <div class="news-title">
  388. <a href="https://wallstreetcn.com/articles/3749141" target="_blank" class="news-link">恒生生科指数1月以来涨超60%,中国创新药的"DeepSeek时刻"超过了AI</a>
  389. </div>
  390. </div>
  391. </div>
  392. </div>
  393. <div class="word-group">
  394. <div class="word-header">
  395. <div class="word-info">
  396. <div class="word-name">哪吒 饺子</div>
  397. <div class="word-count">1 条</div>
  398. </div>
  399. <div class="word-index">3/4</div>
  400. </div>
  401. <div class="news-item">
  402. <div class="news-number">1</div>
  403. <div class="news-content">
  404. <div class="news-header">
  405. <span class="source-name">百度热搜</span>
  406. <span class="rank-num">24-30</span>
  407. <span class="time-info">00:57~06:55</span>
  408. <span class="count-info">7次</span>
  409. </div>
  410. <div class="news-title">
  411. <a href="https://www.baidu.com/s?wd=%E3%80%8A%E5%93%AA%E5%90%922%E3%80%8B%E7%89%87%E6%96%B9%E6%88%96%E5%88%86%E8%B4%A652%E4%BA%BF%E5%85%83" target="_blank" class="news-link">《哪吒2》片方或分账52亿元</a>
  412. </div>
  413. </div>
  414. </div>
  415. </div>
  416. <div class="word-group">
  417. <div class="word-header">
  418. <div class="word-info">
  419. <div class="word-name">米哈游 原神 星穹铁道</div>
  420. <div class="word-count">1 条</div>
  421. </div>
  422. <div class="word-index">4/4</div>
  423. </div>
  424. <div class="news-item">
  425. <div class="news-number">1</div>
  426. <div class="news-content">
  427. <div class="news-header">
  428. <span class="source-name">zhihu</span>
  429. <span class="rank-num top">5</span>
  430. <span class="time-info">06:55~07:17</span>
  431. <span class="count-info">2次</span>
  432. </div>
  433. <div class="news-title">
  434. <a href="https://www.zhihu.com/question/1905395386765537540" target="_blank" class="news-link">目前原神所有自机角色谁最有可能出新形态?</a>
  435. </div>
  436. </div>
  437. </div>
  438. </div>
  439. </div>
  440. <div class="footer">
  441. <div class="footer-content">
  442. 由 <span class="project-name">TrendRadar</span> 生成 ·
  443. <a href="https://github.com/sansan0/TrendRadar" target="_blank" class="footer-link">
  444. GitHub 开源项目
  445. </a>
  446. </div>
  447. </div>
  448. </div>
  449. <script>
  450. async function saveAsImage() {
  451. const button = event.target;
  452. const originalText = button.textContent;
  453. try {
  454. button.textContent = '生成中...';
  455. button.disabled = true;
  456. window.scrollTo(0, 0);
  457. await new Promise(resolve => setTimeout(resolve, 200));
  458. const buttons = document.querySelector('.save-buttons');
  459. buttons.style.visibility = 'hidden';
  460. await new Promise(resolve => setTimeout(resolve, 100));
  461. const container = document.querySelector('.container');
  462. const canvas = await html2canvas(container, {
  463. backgroundColor: '#ffffff',
  464. scale: 1.5,
  465. useCORS: true,
  466. allowTaint: false,
  467. imageTimeout: 10000,
  468. removeContainer: false,
  469. foreignObjectRendering: false,
  470. logging: false,
  471. width: container.offsetWidth,
  472. height: container.offsetHeight,
  473. x: 0,
  474. y: 0,
  475. scrollX: 0,
  476. scrollY: 0,
  477. windowWidth: window.innerWidth,
  478. windowHeight: window.innerHeight
  479. });
  480. buttons.style.visibility = 'visible';
  481. const link = document.createElement('a');
  482. const now = new Date();
  483. const filename = `TrendRadar_热点新闻分析_${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}.png`;
  484. link.download = filename;
  485. link.href = canvas.toDataURL('image/png', 1.0);
  486. document.body.appendChild(link);
  487. link.click();
  488. document.body.removeChild(link);
  489. button.textContent = '保存成功!';
  490. setTimeout(() => {
  491. button.textContent = originalText;
  492. button.disabled = false;
  493. }, 2000);
  494. } catch (error) {
  495. const buttons = document.querySelector('.save-buttons');
  496. buttons.style.visibility = 'visible';
  497. button.textContent = '保存失败';
  498. setTimeout(() => {
  499. button.textContent = originalText;
  500. button.disabled = false;
  501. }, 2000);
  502. }
  503. }
  504. async function saveAsMultipleImages() {
  505. const button = event.target;
  506. const originalText = button.textContent;
  507. const container = document.querySelector('.container');
  508. const scale = 1.5;
  509. const maxHeight = 5000 / scale;
  510. try {
  511. button.textContent = '分析中...';
  512. button.disabled = true;
  513. const wordGroups = Array.from(container.querySelectorAll('.word-group'));
  514. const header = container.querySelector('.header');
  515. const footer = container.querySelector('.footer');
  516. const containerRect = container.getBoundingClientRect();
  517. const elements = [];
  518. elements.push({
  519. type: 'header',
  520. element: header,
  521. top: 0,
  522. bottom: header.offsetHeight,
  523. height: header.offsetHeight
  524. });
  525. wordGroups.forEach(group => {
  526. const groupRect = group.getBoundingClientRect();
  527. const wordHeader = group.querySelector('.word-header');
  528. if (wordHeader) {
  529. const headerRect = wordHeader.getBoundingClientRect();
  530. elements.push({
  531. type: 'word-header',
  532. top: groupRect.top - containerRect.top,
  533. bottom: headerRect.bottom - containerRect.top,
  534. height: headerRect.height
  535. });
  536. }
  537. group.querySelectorAll('.news-item').forEach(item => {
  538. const rect = item.getBoundingClientRect();
  539. elements.push({
  540. type: 'news-item',
  541. top: rect.top - containerRect.top,
  542. bottom: rect.bottom - containerRect.top,
  543. height: rect.height
  544. });
  545. });
  546. });
  547. const footerRect = footer.getBoundingClientRect();
  548. elements.push({
  549. type: 'footer',
  550. top: footerRect.top - containerRect.top,
  551. bottom: footerRect.bottom - containerRect.top,
  552. height: footer.offsetHeight
  553. });
  554. const segments = [];
  555. let currentSegment = { start: 0, end: 0, height: 0 };
  556. let headerHeight = header.offsetHeight;
  557. currentSegment.height = headerHeight;
  558. for (let i = 1; i < elements.length; i++) {
  559. const element = elements[i];
  560. const potentialHeight = element.bottom - currentSegment.start;
  561. if (potentialHeight > maxHeight && currentSegment.height > headerHeight) {
  562. currentSegment.end = elements[i - 1].bottom;
  563. segments.push(currentSegment);
  564. currentSegment = {
  565. start: currentSegment.end,
  566. end: 0,
  567. height: element.bottom - currentSegment.end
  568. };
  569. } else {
  570. currentSegment.height = potentialHeight;
  571. currentSegment.end = element.bottom;
  572. }
  573. }
  574. if (currentSegment.height > 0) {
  575. currentSegment.end = container.offsetHeight;
  576. segments.push(currentSegment);
  577. }
  578. button.textContent = `生成中 (0/${segments.length})...`;
  579. const buttons = document.querySelector('.save-buttons');
  580. buttons.style.visibility = 'hidden';
  581. const images = [];
  582. for (let i = 0; i < segments.length; i++) {
  583. const segment = segments[i];
  584. button.textContent = `生成中 (${i + 1}/${segments.length})...`;
  585. const tempContainer = document.createElement('div');
  586. tempContainer.style.cssText = `
  587. position: absolute;
  588. left: -9999px;
  589. top: 0;
  590. width: ${container.offsetWidth}px;
  591. background: white;
  592. `;
  593. const clonedContainer = container.cloneNode(true);
  594. const clonedButtons = clonedContainer.querySelector('.save-buttons');
  595. if (clonedButtons) {
  596. clonedButtons.style.display = 'none';
  597. }
  598. tempContainer.appendChild(clonedContainer);
  599. document.body.appendChild(tempContainer);
  600. await new Promise(resolve => setTimeout(resolve, 100));
  601. const canvas = await html2canvas(clonedContainer, {
  602. backgroundColor: '#ffffff',
  603. scale: scale,
  604. useCORS: true,
  605. allowTaint: false,
  606. imageTimeout: 10000,
  607. logging: false,
  608. width: container.offsetWidth,
  609. height: segment.end - segment.start,
  610. x: 0,
  611. y: segment.start,
  612. windowWidth: window.innerWidth,
  613. windowHeight: window.innerHeight
  614. });
  615. images.push(canvas.toDataURL('image/png', 1.0));
  616. document.body.removeChild(tempContainer);
  617. }
  618. buttons.style.visibility = 'visible';
  619. const now = new Date();
  620. const baseFilename = `TrendRadar_热点新闻分析_${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}`;
  621. for (let i = 0; i < images.length; i++) {
  622. const link = document.createElement('a');
  623. link.download = `${baseFilename}_part${i + 1}.png`;
  624. link.href = images[i];
  625. document.body.appendChild(link);
  626. link.click();
  627. document.body.removeChild(link);
  628. await new Promise(resolve => setTimeout(resolve, 100));
  629. }
  630. button.textContent = `已保存 ${segments.length} 张图片!`;
  631. setTimeout(() => {
  632. button.textContent = originalText;
  633. button.disabled = false;
  634. }, 2000);
  635. } catch (error) {
  636. console.error('分段保存失败:', error);
  637. const buttons = document.querySelector('.save-buttons');
  638. buttons.style.visibility = 'visible';
  639. button.textContent = '保存失败';
  640. setTimeout(() => {
  641. button.textContent = originalText;
  642. button.disabled = false;
  643. }, 2000);
  644. }
  645. }
  646. document.addEventListener('DOMContentLoaded', function() {
  647. window.scrollTo(0, 0);
  648. });
  649. </script>
  650. </body>
  651. </html>