| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- import React, { useEffect, useState } from 'react'
- const PLATFORMS = [
- ['xiaohongshu', '小红书', 'xhs'],
- ['weixin', '微信公众号', 'wx'],
- ['douyin', '抖音', 'dy'],
- ]
- function shortText(text, n = 220) {
- if (!text) return ''
- return text.length > n ? text.slice(0, n) + '...' : text
- }
- function clsLabel(cls) {
- if (!cls || cls.is_creation === null || cls.is_creation === undefined) {
- return ['none', cls?.error ? '判断失败' : '未判断']
- }
- return cls.is_creation ? ['yes', '创作知识'] : ['no', '非创作知识']
- }
- function PlatformStatus({ platform }) {
- const status = platform?.status || 'not_started'
- const text = {
- not_started: '未跑',
- pending: '等待中',
- running: '运行中',
- done: '完成',
- partial: '部分完成',
- failed: '失败',
- }[status] || status
- return <span className={`status ${status}`}>{text}</span>
- }
- function Media({ item, platform, onImageClick }) {
- if (platform === 'douyin' && item.video_url) {
- return <video controls playsInline src={item.video_url} poster={item.cover_url || undefined} />
- }
- const images = item.image_urls || []
- if (images.length > 1) {
- return (
- <div className="thumbstrip" aria-label="帖子图片">
- {images.map((u, i) => (
- <button className="thumbbtn" key={i} type="button" onClick={() => onImageClick?.(images, i, item.title)}>
- <img className="gimg" src={u} alt={`第 ${i + 1} 张`} loading="lazy" />
- <span>{i + 1}</span>
- </button>
- ))}
- </div>
- )
- }
- const src = images[0] || item.cover_url
- return src ? (
- <button className="coverbtn" type="button" onClick={() => onImageClick?.([src], 0, item.title)}>
- <img className="cover" src={src} alt="" loading="lazy" />
- </button>
- ) : null
- }
- function ResultCard({ item, platform, tone, onImageClick }) {
- const [clsTone, clsText] = clsLabel(item.classification)
- const hasKnowledge = Boolean(item.classification?.knowledge)
- return (
- <div className={`lane ${tone}`}>
- <Media item={item} platform={platform} onImageClick={onImageClick} />
- <div className="card-main">
- <div className="t">{item.title || '无标题'}</div>
- <span className={`cls ${clsTone}`}>{clsText}</span>
- </div>
- {item.author && <div className="meta">{item.author}</div>}
- {item.classification?.reason && <div className="meta">{item.classification.reason}</div>}
- {item.body_text && (
- <details className="fold">
- <summary>正文摘要</summary>
- <div className="bt">{shortText(item.body_text, 520)}</div>
- </details>
- )}
- {hasKnowledge && (
- <details className="fold knfold">
- <summary>创作知识</summary>
- <div className="knbody">{item.classification.knowledge}</div>
- </details>
- )}
- {item.url && <a className="src" href={item.url} target="_blank" rel="noreferrer">打开原链接</a>}
- </div>
- )
- }
- function PlatformGroup({ id, label, tone, data, onImageClick }) {
- const items = data?.items || []
- const creationItems = items.filter(item => item.classification?.is_creation === 1)
- const otherItems = items.filter(item => item.classification?.is_creation !== 1)
- const renderRow = (rowItems, title, dim = false) => (
- <div className="result-band">
- <div className={`subhd ${dim ? 'dim' : ''}`}>
- <span>{title}</span>
- <span className="subn">{rowItems.length}</span>
- </div>
- {rowItems.length === 0 ? (
- <div className="row-empty">{dim ? '暂无非创作知识或判断失败' : '暂无创作知识'}</div>
- ) : (
- <div className="grid result-row">
- {rowItems.map(item => <ResultCard key={item.id} item={item} platform={id} tone={tone} onImageClick={onImageClick} />)}
- </div>
- )}
- </div>
- )
- return (
- <section className="pgroup">
- <div className={`ghead ${tone}`}>
- <span>{label}</span>
- <PlatformStatus platform={data} />
- <span className="gn">{items.length}/5 条</span>
- </div>
- {data?.error && <div className="perr">{data.error}</div>}
- {items.length === 0 ? (
- <div className="gnone">暂无可展示内容</div>
- ) : (
- <>
- {renderRow(creationItems, '创作知识')}
- {renderRow(otherItems, '非创作知识 / 判断失败', true)}
- </>
- )}
- </section>
- )
- }
- export default function CreationQueryDetail({ query }) {
- const [data, setData] = useState(null)
- const [err, setErr] = useState('')
- const [lightbox, setLightbox] = useState(null)
- const openImage = (images, index, title) => {
- setLightbox({ images, index, title: title || '帖子图片' })
- }
- const moveImage = (delta) => {
- setLightbox(prev => {
- if (!prev) return prev
- const next = (prev.index + delta + prev.images.length) % prev.images.length
- return { ...prev, index: next }
- })
- }
- useEffect(() => {
- setData(null)
- setErr('')
- fetch('/api/creation-search/query?display_limit=5&query=' + encodeURIComponent(query))
- .then(r => r.ok ? r.json() : Promise.reject(new Error('接口读取失败')))
- .then(setData)
- .catch(e => setErr(e.message || '读取失败'))
- }, [query])
- return (
- <div>
- <div className="detail-top">
- <a className="back" href="#/">返回 Query Demo</a>
- {data?.run_id && <span className="meta">run_id: {data.run_id}</span>}
- </div>
- <h1 className="dq">{query}</h1>
- {err && <div className="empty">{err}</div>}
- {!data && !err && <div className="empty">加载中...</div>}
- {data && PLATFORMS.map(([id, label, tone]) => (
- <PlatformGroup key={id} id={id} label={label} tone={tone} data={data.platforms?.[id]} onImageClick={openImage} />
- ))}
- {lightbox && (
- <div className="lightbox" onClick={() => setLightbox(null)}>
- <button className="lbx" type="button" onClick={() => setLightbox(null)}>×</button>
- {lightbox.images.length > 1 && (
- <button className="lbnav prev" type="button" onClick={(e) => { e.stopPropagation(); moveImage(-1) }}>‹</button>
- )}
- <figure className="lbfig" onClick={(e) => e.stopPropagation()}>
- <img src={lightbox.images[lightbox.index]} alt="" />
- <figcaption>{lightbox.title} · {lightbox.index + 1}/{lightbox.images.length}</figcaption>
- </figure>
- {lightbox.images.length > 1 && (
- <button className="lbnav next" type="button" onClick={(e) => { e.stopPropagation(); moveImage(1) }}>›</button>
- )}
- </div>
- )}
- </div>
- )
- }
|