Files
stash/stash-web/src/components/content/HomeView.tsx
T
CNWei 19ead7b716 feat(web): 地址栏驱动视图,刷新详情页不再回首页
- 新增 data/view.ts 与 useViewRoute:视图与地址双向同步,支持前进后退
- 条目地址带上所属分组,刷新 / 收藏后「返回」有确定落点
- 服务端只把 /c/** 回落到 index.html,其余路径仍返回 404
- 明细页返回按钮去掉左侧箭头图标
2026-10-24 17:18:30 +08:00

93 lines
3.1 KiB
TypeScript

import { categoryCount, flattenAppItems, type Category } from '../../data/types'
import { AppGrid } from './AppGrid'
import { ArticleGrid } from './ArticleGrid'
import { PosterGrid } from './PosterGrid'
import { SectionCard } from './SectionCard'
/**
* 首页:把每个分类渲染成一张分区卡片。
*
* 每个分区只展示前若干条(由分类的 previewLimit 决定),
* 右上角 ••• 进入该分类明细,点击卡片 / 海报则进入该条目的详情。
*/
type HomeViewProps = {
categories: Category[]
onOpenDetail: (categoryId: string) => void
onOpenItem: (categoryId: string, itemId: string) => void
}
export function HomeView({ categories, onOpenDetail, onOpenItem }: HomeViewProps) {
return (
<div className="home-view">
{categories.map((category) => (
<SectionCard
key={category.id}
// 卡片标题跟着实际展示的内容走:配了展示块就用块标题(如「常用站点」),
// 没配(如「本地服务」)才用分类标题
title={category.block?.title ?? category.title}
count={cardCount(category)}
barColor={category.barColor}
onMore={() => onOpenDetail(category.id)}
>
{renderPreview(category, onOpenItem)}
</SectionCard>
))}
</div>
)
}
/**
* 卡片右上角的数量角标。
*
* 配了「分组展示块」时,卡片实际展示的是那个分组,所以数量也应该只数它;
* 否则(电影 / 文章 / 本地服务)数整个分区。
*/
function cardCount(category: Category): number {
if (category.kind === 'apps' && category.block !== undefined) {
const group = category.groups.find((item) => item.id === category.block?.nodeKey)
if (group !== undefined) return group.items.length
}
return categoryCount(category)
}
/** 按分类类型输出对应的预览网格;应用类为空时给出提示文案 */
function renderPreview(
category: Category,
onOpenItem: (categoryId: string, itemId: string) => void,
) {
if (category.kind === 'apps') {
// 配了展示块就只展示那个分组;没有配置(如「本地服务」)则退回全部条目
const blockGroup =
category.block !== undefined
? category.groups.find((group) => group.id === category.block?.nodeKey)
: undefined
const source = category.block !== undefined ? (blockGroup?.items ?? []) : flattenAppItems(category)
const limit = category.block?.previewLimit ?? category.previewLimit ?? 4
const items = source.slice(0, limit)
if (items.length === 0) {
return <p className="section-empty">{category.emptyText ?? '暂无内容'}</p>
}
return <AppGrid items={items} onOpen={(itemId) => onOpenItem(category.id, itemId)} />
}
if (category.kind === 'posters') {
return (
<PosterGrid
items={category.items.slice(0, category.previewLimit ?? 6)}
onOpen={(itemId) => onOpenItem(category.id, itemId)}
/>
)
}
return (
<ArticleGrid
items={category.items.slice(0, category.previewLimit ?? 4)}
onOpen={(itemId) => onOpenItem(category.id, itemId)}
/>
)
}