SearchResultColumn.jsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import { useMemo, useState } from 'react'
  2. import { flatDetailItems, isFinalKnowledge, itemBrief, itemMediaUrl } from './model.js'
  3. const PLATFORM_FILTERS = [
  4. ['all', '全部'],
  5. ['weixin', '微信'],
  6. ['xiaohongshu', '小红书'],
  7. ['douyin', '抖音'],
  8. ]
  9. export default function SearchResultColumn({ selected, detail, loading, error, onKnowledgeClick }) {
  10. const [platformFilter, setPlatformFilter] = useState('all')
  11. const allItems = useMemo(() => {
  12. return selected ? [...flatDetailItems(detail)].sort((a, b) => {
  13. const ak = isFinalKnowledge(a) ? 0 : 1
  14. const bk = isFinalKnowledge(b) ? 0 : 1
  15. if (ak !== bk) return ak - bk
  16. return (a.platform || '').localeCompare(b.platform || '') || (a.title || '').localeCompare(b.title || '')
  17. }) : []
  18. }, [detail, selected])
  19. const platformCounts = useMemo(() => {
  20. return allItems.reduce((counts, item) => {
  21. counts[item.platform] = (counts[item.platform] || 0) + 1
  22. return counts
  23. }, {})
  24. }, [allItems])
  25. const items = platformFilter === 'all'
  26. ? allItems
  27. : allItems.filter((item) => item.platform === platformFilter)
  28. const totalCount = allItems.length
  29. const selectedFilterLabel = PLATFORM_FILTERS.find(([key]) => key === platformFilter)?.[1] || '全部'
  30. return (
  31. <div className="axcol qresult-col">
  32. <div className="axhd qresult-hd">
  33. <div className="qresult-title-row">
  34. <div className="axis-title">
  35. <span className="query-search-icon">帖</span>
  36. <span>
  37. 搜索结果{selected ? `(${platformFilter === 'all' ? totalCount : `${selectedFilterLabel} ${items.length}/${totalCount}`}贴)` : ''}
  38. </span>
  39. </div>
  40. <div className="platform-filter" aria-label="按平台筛选搜索结果">
  41. {PLATFORM_FILTERS.map(([key, label]) => {
  42. const count = key === 'all' ? totalCount : platformCounts[key] || 0
  43. return (
  44. <button
  45. className={platformFilter === key ? 'on' : ''}
  46. disabled={!selected || count === 0}
  47. key={key}
  48. type="button"
  49. onClick={() => setPlatformFilter(key)}
  50. >
  51. {label}<span>{count}</span>
  52. </button>
  53. )
  54. })}
  55. </div>
  56. </div>
  57. <div className="qresult-sub">
  58. {selected ? selected.query : '选择 Query 查看帖子'}
  59. </div>
  60. </div>
  61. <div className="qresult-list">
  62. {!selected && <div className="qresult-empty">点击左侧 Query 词后,这里展示本次搜到的帖子。</div>}
  63. {selected && loading && <div className="qresult-empty">加载搜索结果...</div>}
  64. {selected && error && <div className="qresult-empty error">{error}</div>}
  65. {selected && !loading && !error && allItems.length === 0 && (
  66. <div className="qresult-empty">这个 Query 暂无搜索结果。若后台还在运行,请等待数据写入。</div>
  67. )}
  68. {selected && !loading && !error && allItems.length > 0 && items.length === 0 && (
  69. <div className="qresult-empty">当前筛选下没有{selectedFilterLabel}帖子。</div>
  70. )}
  71. {items.map((item) => {
  72. const knowledge = isFinalKnowledge(item)
  73. const cover = itemMediaUrl(item)
  74. const confidence = item.classification?.confidence
  75. return (
  76. <article className={`result-mini ${knowledge ? 'knowledge' : ''}`} key={item.id}>
  77. <button
  78. className="result-mini-click"
  79. type="button"
  80. onClick={() => { window.location.hash = `/item/${item.id}` }}
  81. >
  82. {cover && <img src={cover} alt="" loading="lazy" />}
  83. <div className="result-mini-main">
  84. <div className="result-mini-title">{item.title || '无标题'}</div>
  85. <div className="result-mini-tags">
  86. <span className={`platform-mini ${item.platform_tone}`}>{item.platform_label}</span>
  87. <span className={knowledge ? 'knowledge-mini yes' : 'knowledge-mini'}>{knowledge ? '创作知识' : '未形成知识'}</span>
  88. {typeof confidence === 'number' && <span className="score-mini">{confidence.toFixed(2)}</span>}
  89. </div>
  90. {itemBrief(item) && <div className="result-mini-text">{itemBrief(item)}</div>}
  91. </div>
  92. </button>
  93. <div className="result-mini-actions">
  94. <button
  95. className="mini-detail-btn detail"
  96. type="button"
  97. onClick={() => { window.location.hash = `/item/${item.id}` }}
  98. >
  99. 查看详情
  100. </button>
  101. {knowledge && (
  102. <button
  103. className="mini-detail-btn knowledge"
  104. type="button"
  105. onClick={() => onKnowledgeClick?.(item.id)}
  106. >
  107. 查看创作知识
  108. </button>
  109. )}
  110. </div>
  111. </article>
  112. )
  113. })}
  114. </div>
  115. </div>
  116. )
  117. }