Files
stash/stash-web/src/components/header/SiteHeader.tsx
T
CNWei 4ce1b7e7c1 feat(cards): 卡片外观可配置(不透明度 / 毛玻璃)
- 站点设置新增 card.opacity(40–100)与 card.blur,后台「首页管理 → 卡片外观」可调
- 前台所有卡片(首页 / 分组页 / 详情 / 侧栏)共用一套 CSS 变量
- 顺带带上上一轮:背景缓存、搜索记忆、明暗图标跟随实际状态、卡片描边/阴影、分组页卡片外壳
2026-11-05 12:45:11 +08:00

140 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Brand, Icon, IconButton, Tag, Ticker } from '@stash/ui'
import {
fillHeaderText,
type HeaderItem,
type SettingsTone,
type SiteMetaSettings,
} from '../../settings'
/**
* 顶部栏:左品牌、中间滚动信息(可在 /admin 配置)、右侧操作图标。
* 品牌与操作图标是否出现,都来自「页头」Tab 的站点设置。
* 外壳(品牌、跑马灯)都用组件库,这里只拼内容。
*/
type SiteHeaderProps = {
projectCount: number
/** 用于展示的第一个项目名,没有项目时留空 */
sampleProject?: string
live: boolean
/** 页头滚动信息;为空时用默认三条 */
headerItems: HeaderItem[]
headerEnabled: boolean
/** 滚动一轮的秒数 */
headerSpeed: number
/** 信息条目之间的间距(px) */
headerGap?: number
/** 品牌与操作工具配置 */
site: SiteMetaSettings
/** 站点设置是否已加载;未加载时不渲染依赖配置的图标,避免先显示再消失 */
ready?: boolean
/** 是否已登录:决定右侧是「登录」还是「退出登录」 */
authenticated: boolean
username?: string
/** 刷新中:刷新按钮转圈 */
refreshing?: boolean
/** 当前是否为深色,决定明暗按钮的图标与文案 */
dark?: boolean
onRefresh: () => void
onToggleTheme: () => void
onLogin: () => void
onLogout: () => void
onSettings: () => void
}
const tagTone: Record<SettingsTone, 'neutral' | 'primary' | 'success'> = {
plain: 'neutral',
neutral: 'neutral',
primary: 'primary',
success: 'success',
}
export function SiteHeader({
projectCount,
sampleProject,
live,
headerItems,
headerEnabled,
headerSpeed,
headerGap,
site,
ready = true,
authenticated,
username,
refreshing = false,
dark = false,
onRefresh,
onToggleTheme,
onLogin,
onLogout,
onSettings,
}: SiteHeaderProps) {
const context = { count: projectCount, project: sampleProject, live }
return (
<div className="site-header-inner">
<Brand
href="/"
name={site.brand.name}
mark={site.brand.mark}
logo={site.brand.logo}
/>
<div className="header-ticker-slot">
{ready && headerEnabled && (
<Ticker speed={headerSpeed} gap={headerGap} label="运行状态" className="header-ticker">
{headerItems.map((item, index) => (
<span className="header-ticker-item" key={`${index}-${item.text}`}>
{item.tag !== undefined && item.tag !== null && item.tag !== '' && (
<Tag tone={tagTone[item.tone]} shape="rounded">
{item.tag}
</Tag>
)}
{fillHeaderText(item.text, context)}
</span>
))}
</Ticker>
)}
</div>
<div className="header-actions">
{ready && (
<>
{site.actions.theme && (
<IconButton
label={dark ? '切换到浅色' : '切换到深色'}
icon={<Icon name={dark ? 'moon_line' : 'sun_line'} />}
onClick={onToggleTheme}
/>
)}
{site.actions.refresh && (
<IconButton
label="刷新(重扫项目并重拉内容)"
icon={<Icon name="lightning_line" />}
spinning={refreshing}
onClick={onRefresh}
/>
)}
{site.actions.settings && authenticated && (
<IconButton
label="站点设置"
icon={<Icon name="settings_1_line" />}
onClick={onSettings}
/>
)}
{site.actions.session &&
(authenticated ? (
<IconButton
label={username === undefined ? '退出登录' : `退出登录(${username})`}
icon={<Icon name="exit_line" />}
onClick={onLogout}
/>
) : (
<IconButton label="登录" icon={<Icon name="user_3_line" />} onClick={onLogin} />
))}
</>
)}
</div>
</div>
)
}