import { Breadcrumb, Card, Icon, IconButton, Paragraph, Space, Tag, Text, Title, toast, type BreadcrumbItem, } from '@stash/ui' import { useEffect, useState } from 'react' import type { ItemDetailModel } from '../../data/detail' import { resolveHref } from '../../data/detail' import type { RelatedItem } from '../../data/types' import { RelatedList } from './RelatedList' /** * 条目详情页:应用与电影共用。 * * 版式:面包屑 + 标题 + 主图(方形头像 / 竖版海报)+ 标签行 + 一句话评价 + 正文 * + 操作条(浏览 / 点赞 / 分享 / 跳转)+ 可选横幅 + 「推荐阅读」。 * 具体形态由 ItemDetailModel 决定,这里不判断条目类型。 */ type ItemDetailViewProps = { /** 条目业务键:浏览 / 点赞要写回数据库 */ itemKey: string crumbs: BreadcrumbItem[] model: ItemDetailModel related: RelatedItem[] onNavigate: (href: string) => void onOpenItem: (itemId: string) => void /** 打开编辑 Drawer */ onEdit: () => void /** 只有登录后才显示 [编辑] */ canEdit: boolean } export function ItemDetailView({ itemKey, crumbs, model, related, onNavigate, onOpenItem, onEdit, canEdit, }: ItemDetailViewProps) { const [views, setViews] = useState(model.views) const [likes, setLikes] = useState(model.likes) // 历史数据可能是裸域名或占位符 #;统一解析成可跳转地址后再渲染 const jumpHref = resolveHref(model.href) const [liked, setLiked] = useState(() => { try { return window.localStorage.getItem(`stash-liked:${itemKey}`) === '1' } catch { return false } }) // 进入详情页记一次浏览(每个页面打开一次) useEffect(() => { let alive = true fetch(`/api/items/${encodeURIComponent(itemKey)}/view`, { method: 'POST', credentials: 'same-origin', }) .then((response) => (response.ok ? response.json() : null)) .then((payload: { views?: number; likes?: number } | null) => { if (!alive || payload === null) return if (typeof payload.views === 'number') setViews(payload.views) if (typeof payload.likes === 'number') setLikes(payload.likes) }) .catch(() => undefined) return () => { alive = false } }, [itemKey]) const toggleLike = () => { const next = !liked setLiked(next) setLikes((current) => Math.max(0, current + (next ? 1 : -1))) try { window.localStorage.setItem(`stash-liked:${itemKey}`, next ? '1' : '0') } catch { // 隐私模式下 localStorage 不可用,忽略即可 } fetch(`/api/items/${encodeURIComponent(itemKey)}/like`, { method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ liked: next }), }) .then((response) => (response.ok ? response.json() : null)) .then((payload: { views?: number; likes?: number } | null) => { if (payload === null) return if (typeof payload.views === 'number') setViews(payload.views) if (typeof payload.likes === 'number') setLikes(payload.likes) }) .catch(() => undefined) } const share = () => { const url = window.location.href const clipboard = navigator.clipboard if (clipboard === undefined) { toast.error('当前浏览器不支持复制,请手动复制地址栏') return } void clipboard .writeText(url) .then(() => toast.success('链接已复制')) .catch(() => toast.error('复制失败,请手动复制地址栏')) } return (
{model.comment}
)} {model.paragraphs.map((paragraph, index) => (