feat(admin): 首页卡片配置 + 页头开关验证 + 编辑按钮仅登录可见
- settings.sections:显隐 / 顺序 / 标题覆盖 / 预览条数,HomeView 应用 - /admin 新增「首页卡片」Tab,卡片标题使用展示块标题(常用站点等) - 详情页 [编辑] 仅在登录后渲染(canEdit) - 测试:46 passed;页头禁用、卡片隐藏/改标题/限条数、编辑可见性均实测 - 文档:ADMIN-PLAN、README
This commit is contained in:
@@ -16,6 +16,8 @@ type ArticleDetailViewProps = {
|
||||
categoryTitle: string
|
||||
onNavigate: (href: string) => void
|
||||
onEdit: () => void
|
||||
/** 只有登录后才显示 [编辑] */
|
||||
canEdit: boolean
|
||||
}
|
||||
|
||||
export function ArticleDetailView({
|
||||
@@ -24,6 +26,7 @@ export function ArticleDetailView({
|
||||
categoryTitle,
|
||||
onNavigate,
|
||||
onEdit,
|
||||
canEdit,
|
||||
}: ArticleDetailViewProps) {
|
||||
const meta = article.meta ?? ['2 年前', categoryTitle]
|
||||
const crumbs = [
|
||||
@@ -41,9 +44,11 @@ export function ArticleDetailView({
|
||||
<header className="article-head">
|
||||
<Title level={1} as="h2" className="article-heading">
|
||||
{article.title}
|
||||
<button type="button" className="item-edit" title="编辑文章" onClick={onEdit}>
|
||||
[编辑]
|
||||
</button>
|
||||
{canEdit && (
|
||||
<button type="button" className="item-edit" title="编辑文章" onClick={onEdit}>
|
||||
[编辑]
|
||||
</button>
|
||||
)}
|
||||
</Title>
|
||||
|
||||
<div className="article-meta">
|
||||
|
||||
@@ -4,33 +4,46 @@ import { ArticleGrid } from './ArticleGrid'
|
||||
import { Empty } from '@stash/ui'
|
||||
import { PosterGrid } from './PosterGrid'
|
||||
import { SectionCard } from './SectionCard'
|
||||
import {
|
||||
applySectionSettings,
|
||||
sectionLimit,
|
||||
sectionTitle,
|
||||
type SectionSettings,
|
||||
} from '../../settings'
|
||||
|
||||
/**
|
||||
* 首页:把每个分类渲染成一张分区卡片。
|
||||
*
|
||||
* 每个分区只展示前若干条(由分类的 previewLimit 决定),
|
||||
* 右上角 ••• 进入该分类明细,点击卡片 / 海报则进入该条目的详情。
|
||||
* 每个分区只展示前若干条;标题、条数、顺序与显隐都可以在 `/admin`
|
||||
* 的「首页卡片」里覆盖(见 settings.sections)。
|
||||
*/
|
||||
type HomeViewProps = {
|
||||
categories: Category[]
|
||||
sections: SectionSettings
|
||||
onOpenDetail: (categoryId: string) => void
|
||||
onOpenItem: (categoryId: string, itemId: string) => void
|
||||
}
|
||||
|
||||
export function HomeView({ categories, onOpenDetail, onOpenItem }: HomeViewProps) {
|
||||
export function HomeView({ categories, sections, onOpenDetail, onOpenItem }: HomeViewProps) {
|
||||
const cards = applySectionSettings(categories, sections)
|
||||
|
||||
return (
|
||||
<div className="home-view">
|
||||
{categories.map((category) => (
|
||||
{cards.map((category) => (
|
||||
<SectionCard
|
||||
key={category.id}
|
||||
// 卡片标题跟着实际展示的内容走:配了展示块就用块标题(如「常用站点」),
|
||||
// 没配(如「本地服务」)才用分类标题
|
||||
title={category.block?.title ?? category.title}
|
||||
// 没配(如「本地服务」)才用分类标题;设置里的覆盖值优先
|
||||
title={sectionTitle(
|
||||
category.id,
|
||||
category.block?.title ?? category.title,
|
||||
sections,
|
||||
)}
|
||||
count={cardCount(category)}
|
||||
barColor={category.barColor}
|
||||
onMore={() => onOpenDetail(category.id)}
|
||||
>
|
||||
{renderPreview(category, onOpenItem)}
|
||||
{renderPreview(category, sections, onOpenItem)}
|
||||
</SectionCard>
|
||||
))}
|
||||
</div>
|
||||
@@ -55,6 +68,7 @@ function cardCount(category: Category): number {
|
||||
/** 按分类类型输出对应的预览网格;应用类为空时给出提示文案 */
|
||||
function renderPreview(
|
||||
category: Category,
|
||||
sections: SectionSettings,
|
||||
onOpenItem: (categoryId: string, itemId: string) => void,
|
||||
) {
|
||||
if (category.kind === 'apps') {
|
||||
@@ -65,7 +79,8 @@ function renderPreview(
|
||||
: undefined
|
||||
|
||||
const source = category.block !== undefined ? (blockGroup?.items ?? []) : flattenAppItems(category)
|
||||
const limit = category.block?.previewLimit ?? category.previewLimit ?? 4
|
||||
const fallback = category.block?.previewLimit ?? category.previewLimit ?? 4
|
||||
const limit = sectionLimit(category.id, fallback, sections)
|
||||
const items = source.slice(0, limit)
|
||||
|
||||
if (items.length === 0) {
|
||||
@@ -78,7 +93,7 @@ function renderPreview(
|
||||
if (category.kind === 'posters') {
|
||||
return (
|
||||
<PosterGrid
|
||||
items={category.items.slice(0, category.previewLimit ?? 6)}
|
||||
items={category.items.slice(0, sectionLimit(category.id, category.previewLimit ?? 6, sections))}
|
||||
onOpen={(itemId) => onOpenItem(category.id, itemId)}
|
||||
/>
|
||||
)
|
||||
@@ -86,7 +101,7 @@ function renderPreview(
|
||||
|
||||
return (
|
||||
<ArticleGrid
|
||||
items={category.items.slice(0, category.previewLimit ?? 4)}
|
||||
items={category.items.slice(0, sectionLimit(category.id, category.previewLimit ?? 4, sections))}
|
||||
onOpen={(itemId) => onOpenItem(category.id, itemId)}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -29,6 +29,8 @@ type ItemDetailViewProps = {
|
||||
onOpenItem: (itemId: string) => void
|
||||
/** 打开编辑 Drawer */
|
||||
onEdit: () => void
|
||||
/** 只有登录后才显示 [编辑] */
|
||||
canEdit: boolean
|
||||
}
|
||||
|
||||
export function ItemDetailView({
|
||||
@@ -38,6 +40,7 @@ export function ItemDetailView({
|
||||
onNavigate,
|
||||
onOpenItem,
|
||||
onEdit,
|
||||
canEdit,
|
||||
}: ItemDetailViewProps) {
|
||||
return (
|
||||
<div className="detail-view item-view">
|
||||
@@ -48,9 +51,11 @@ export function ItemDetailView({
|
||||
<Card padding="none" className="item-card">
|
||||
<Title level={1} as="h2" weight="semibold" className="item-title">
|
||||
{model.title}
|
||||
<button type="button" className="item-edit" title="编辑条目" onClick={onEdit}>
|
||||
[编辑]
|
||||
</button>
|
||||
{canEdit && (
|
||||
<button type="button" className="item-edit" title="编辑条目" onClick={onEdit}>
|
||||
[编辑]
|
||||
</button>
|
||||
)}
|
||||
</Title>
|
||||
|
||||
<div className={`item-cover item-cover--${model.shape}`}>
|
||||
|
||||
Reference in New Issue
Block a user