style(admin): 统一标题层级与页面排版
- 每个后台页面新增统一的加粗标题 + 说明 + 右侧操作区 - 分组/数据页操作按钮移到标题右侧,取消正文里的『说明:』散排 - 页头/资料/系统改为 820px 窄栏,标签加粗,保存按钮与表单对齐 - 系统管理拆成『修改密码 / 数据备份』两段,按钮不再整行拉伸 - 外层标题改为『后台管理』,侧栏与卡片页去掉重复说明,卡片列表标题加粗
This commit is contained in:
+62
-1
@@ -787,7 +787,67 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.admin-form {
|
.admin-form {
|
||||||
margin-top: var(--su-space-5);
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-form-narrow {
|
||||||
|
max-width: 820px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 每个后台页面统一的标题区 */
|
||||||
|
.admin-panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--su-space-5);
|
||||||
|
padding-bottom: var(--su-space-4);
|
||||||
|
border-bottom: 1px solid var(--su-border);
|
||||||
|
margin-bottom: var(--su-space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-panel-heading {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-panel-title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--su-text-main);
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.3;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-panel-desc {
|
||||||
|
max-width: 760px;
|
||||||
|
margin: var(--su-space-2) 0 0;
|
||||||
|
color: var(--su-text-muted);
|
||||||
|
font-size: var(--su-font-size-sm);
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-panel-actions {
|
||||||
|
display: flex;
|
||||||
|
flex: none;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--su-space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单标签统一加粗,避免组件默认字重在不同字体下发虚 */
|
||||||
|
.admin-panel label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-inline-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--su-space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-form-divider {
|
||||||
|
margin: var(--su-space-1) 0;
|
||||||
|
border-top: 1px dashed var(--su-border-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-row {
|
.admin-row {
|
||||||
@@ -896,6 +956,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.admin-actions {
|
.admin-actions {
|
||||||
|
max-width: 820px;
|
||||||
margin-top: var(--su-space-6);
|
margin-top: var(--su-space-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
type IconName,
|
type IconName,
|
||||||
type MenuItem,
|
type MenuItem,
|
||||||
} from '@stash/ui'
|
} from '@stash/ui'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
|
||||||
import { changePassword, fetchSession, logout, type SessionState } from '../../auth'
|
import { changePassword, fetchSession, logout, type SessionState } from '../../auth'
|
||||||
import { LoginDialog } from '../auth/LoginDialog'
|
import { LoginDialog } from '../auth/LoginDialog'
|
||||||
import { GroupMark as GroupGlyph } from '../common/GroupMark'
|
import { GroupMark as GroupGlyph } from '../common/GroupMark'
|
||||||
@@ -127,6 +127,27 @@ function flattenCardsGroups(groups: GroupNode[]) {
|
|||||||
return flattenGroups(groups)
|
return flattenGroups(groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 后台每个页面统一的标题区:加粗标题 + 说明 + 右侧操作 */
|
||||||
|
function PanelHeader({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
children?: ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<header className="admin-panel-head">
|
||||||
|
<div className="admin-panel-heading">
|
||||||
|
<h2 className="admin-panel-title">{title}</h2>
|
||||||
|
{description !== undefined && <p className="admin-panel-desc">{description}</p>}
|
||||||
|
</div>
|
||||||
|
{children !== undefined && <div className="admin-panel-actions">{children}</div>}
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function AdminNext() {
|
export function AdminNext() {
|
||||||
const [session, setSession] = useState<SessionState | null>(null)
|
const [session, setSession] = useState<SessionState | null>(null)
|
||||||
const [loginOpen, setLoginOpen] = useState(false)
|
const [loginOpen, setLoginOpen] = useState(false)
|
||||||
@@ -292,9 +313,9 @@ export function AdminNext() {
|
|||||||
<main className="admin-main">
|
<main className="admin-main">
|
||||||
<Card padding="lg">
|
<Card padding="lg">
|
||||||
<Space direction="column" gap="sm" align="stretch">
|
<Space direction="column" gap="sm" align="stretch">
|
||||||
<Title level={2}>站点设置</Title>
|
<Title level={2}>后台管理</Title>
|
||||||
<Text size="sm" tone="muted">
|
<Text size="sm" tone="muted">
|
||||||
分组是唯一容器;侧边栏和内容卡片都只是勾选分组来组合展示。
|
管理内容、首页展示与站点设置。
|
||||||
</Text>
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
@@ -332,7 +353,10 @@ export function AdminNext() {
|
|||||||
|
|
||||||
{tab === 'home-sidebar' && (
|
{tab === 'home-sidebar' && (
|
||||||
<section className="admin-home-section">
|
<section className="admin-home-section">
|
||||||
<Title level={4}>侧边栏</Title>
|
<PanelHeader
|
||||||
|
title="侧边栏"
|
||||||
|
description="控制资料卡、本地服务与分组导航是否显示;分组本身的显隐由「分组管理」决定。"
|
||||||
|
/>
|
||||||
<SidebarPanel
|
<SidebarPanel
|
||||||
value={draft.sidebar}
|
value={draft.sidebar}
|
||||||
saving={saving}
|
saving={saving}
|
||||||
@@ -352,11 +376,10 @@ export function AdminNext() {
|
|||||||
|
|
||||||
{tab === 'home-cards' && (
|
{tab === 'home-cards' && (
|
||||||
<section className="admin-home-section">
|
<section className="admin-home-section">
|
||||||
<Title level={4}>内容卡片</Title>
|
<PanelHeader
|
||||||
<Text size="sm" tone="muted">
|
title="内容卡片"
|
||||||
说明:卡片是首页上的一个区块;在这里勾选它包含哪些分组,并调整这些分组的先后顺序与预览条数。
|
description="卡片是首页上的一个区块。勾选它包含的分组,并调整分组顺序与预览条数;停用的分组不会出现在卡片里。"
|
||||||
分组是否启用仍由「分组管理」决定,停用的分组不会出现在卡片里。
|
/>
|
||||||
</Text>
|
|
||||||
<CardsPanel
|
<CardsPanel
|
||||||
groups={groups}
|
groups={groups}
|
||||||
cards={cards}
|
cards={cards}
|
||||||
@@ -571,18 +594,17 @@ function GroupsPanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction="column" gap="md" align="stretch" className="admin-form">
|
<Space direction="column" gap="md" align="stretch" className="admin-form">
|
||||||
<Text size="sm" tone="muted">
|
<PanelHeader
|
||||||
说明:分组用于控制前台侧边栏与卡片;可多层嵌套,根分组决定格式。内容(条目)只挂在分组上。
|
title="分组管理"
|
||||||
</Text>
|
description="分组用于控制前台侧边栏与内容卡片,可多层嵌套;内容(条目)只挂在分组上。"
|
||||||
|
>
|
||||||
<div className="group-toolbar">
|
|
||||||
<Button
|
<Button
|
||||||
icon={<Icon name="add_line" />}
|
icon={<Icon name="add_line" />}
|
||||||
onClick={() => setDialog({ mode: 'root' })}
|
onClick={() => setDialog({ mode: 'root' })}
|
||||||
>
|
>
|
||||||
新建根分组
|
新建根分组
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</PanelHeader>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -590,7 +612,7 @@ function GroupsPanel({
|
|||||||
columns={[
|
columns={[
|
||||||
{ key: 'name', title: '分组名称' },
|
{ key: 'name', title: '分组名称' },
|
||||||
{ key: 'sort', title: '排序权重', width: 110 },
|
{ key: 'sort', title: '排序权重', width: 110 },
|
||||||
{ key: 'count', title: '关联网页数', width: 120 },
|
{ key: 'count', title: '关联内容数', width: 120 },
|
||||||
{ key: 'status', title: '状态', width: 90 },
|
{ key: 'status', title: '状态', width: 90 },
|
||||||
{ key: 'actions', title: '操作', width: 260, align: 'right' },
|
{ key: 'actions', title: '操作', width: 260, align: 'right' },
|
||||||
]}
|
]}
|
||||||
@@ -982,29 +1004,30 @@ function ContentPanel({ groups, onReload }: { groups: GroupNode[]; onReload: ()
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction="column" gap="md" align="stretch" className="admin-form">
|
<Space direction="column" gap="md" align="stretch" className="admin-form">
|
||||||
<Text size="sm" tone="muted">
|
<PanelHeader
|
||||||
说明:数据只属于一个分组;选中主分组时会连同其所有子分组的数据一起列出。
|
title="数据管理"
|
||||||
</Text>
|
description="每条数据只属于一个分组;选中主分组时会连同其所有子分组的数据一起列出。"
|
||||||
|
>
|
||||||
<div className="entry-toolbar">
|
<div className="entry-toolbar">
|
||||||
<div className="entry-toolbar-select">
|
<div className="entry-toolbar-select">
|
||||||
<Select
|
<Select
|
||||||
options={options.map((option) => ({
|
options={options.map((option) => ({
|
||||||
value: option.key,
|
value: option.key,
|
||||||
label: option.title,
|
label: option.title,
|
||||||
}))}
|
}))}
|
||||||
value={group?.key ?? ''}
|
value={group?.key ?? ''}
|
||||||
onChange={setGroupKey}
|
onChange={setGroupKey}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
icon={<Icon name="add_line" />}
|
||||||
|
disabled={group === undefined}
|
||||||
|
onClick={() => setDialog({ key: null, groupKey })}
|
||||||
|
>
|
||||||
|
新建数据
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
</PanelHeader>
|
||||||
icon={<Icon name="add_line" />}
|
|
||||||
disabled={group === undefined}
|
|
||||||
onClick={() => setDialog({ key: null, groupKey })}
|
|
||||||
>
|
|
||||||
新建数据
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Text size="sm" tone="muted">
|
<Text size="sm" tone="muted">
|
||||||
{group?.title ?? ''} 下共 {rows.length} 条(含子分组)
|
{group?.title ?? ''} 下共 {rows.length} 条(含子分组)
|
||||||
@@ -1333,11 +1356,7 @@ function SidebarPanel({
|
|||||||
saving: boolean
|
saving: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Space direction="column" gap="md" align="stretch">
|
<Space direction="column" gap="md" align="stretch" className="admin-form admin-form-narrow">
|
||||||
<Text size="sm" tone="muted">
|
|
||||||
说明:分组在侧边栏的显隐只由「分组管理 → 状态」决定;这里只管三块侧栏卡片。
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<ToggleRow
|
<ToggleRow
|
||||||
label="本地管理员"
|
label="本地管理员"
|
||||||
checked={value.show_profile !== false}
|
checked={value.show_profile !== false}
|
||||||
@@ -1439,7 +1458,7 @@ function CardsPanel({
|
|||||||
<>
|
<>
|
||||||
<div className="cards-manager">
|
<div className="cards-manager">
|
||||||
<aside className="cards-side">
|
<aside className="cards-side">
|
||||||
<Text size="sm" tone="muted">
|
<Text size="sm" strong>
|
||||||
卡片
|
卡片
|
||||||
</Text>
|
</Text>
|
||||||
<div className="cards-side-new">
|
<div className="cards-side-new">
|
||||||
@@ -1788,7 +1807,11 @@ function HeaderPanel({
|
|||||||
const [tab, setTab] = useState<'site' | 'actions' | 'ticker'>('site')
|
const [tab, setTab] = useState<'site' | 'actions' | 'ticker'>('site')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction="column" gap="lg" align="stretch" className="admin-form">
|
<Space direction="column" gap="lg" align="stretch" className="admin-form admin-form-narrow">
|
||||||
|
<PanelHeader
|
||||||
|
title="页头"
|
||||||
|
description="站点标题、Logo、操作图标与滚动信息。修改后点右下角「保存」生效。"
|
||||||
|
/>
|
||||||
<Tabs
|
<Tabs
|
||||||
className="admin-tabs"
|
className="admin-tabs"
|
||||||
items={[
|
items={[
|
||||||
@@ -2043,7 +2066,11 @@ function ProfilePanel({
|
|||||||
}) {
|
}) {
|
||||||
const update = (patch: Partial<ProfileSettings>) => onChange({ ...value, ...patch })
|
const update = (patch: Partial<ProfileSettings>) => onChange({ ...value, ...patch })
|
||||||
return (
|
return (
|
||||||
<Space direction="column" gap="lg" align="stretch" className="admin-form">
|
<Space direction="column" gap="lg" align="stretch" className="admin-form admin-form-narrow">
|
||||||
|
<PanelHeader
|
||||||
|
title="资料管理"
|
||||||
|
description="这些内容会显示在首页左侧的资料卡上。修改后点右下角「保存」生效。"
|
||||||
|
/>
|
||||||
<FormField label="头像地址" htmlFor="profile-avatar">
|
<FormField label="头像地址" htmlFor="profile-avatar">
|
||||||
<Input
|
<Input
|
||||||
id="profile-avatar"
|
id="profile-avatar"
|
||||||
@@ -2105,7 +2132,15 @@ function SystemPanel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction="column" gap="lg" align="stretch" className="admin-form">
|
<Space direction="column" gap="lg" align="stretch" className="admin-form admin-form-narrow">
|
||||||
|
<PanelHeader
|
||||||
|
title="系统管理"
|
||||||
|
description="账户安全与数据备份。"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text size="sm" strong>
|
||||||
|
修改密码
|
||||||
|
</Text>
|
||||||
<FormField label="当前密码" htmlFor="current-password">
|
<FormField label="当前密码" htmlFor="current-password">
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
id="current-password"
|
id="current-password"
|
||||||
@@ -2125,12 +2160,25 @@ function SystemPanel() {
|
|||||||
{message.text}
|
{message.text}
|
||||||
</Notice>
|
</Notice>
|
||||||
)}
|
)}
|
||||||
<Button variant="secondary" loading={busy} icon={<Icon name="key_2_line" />} onClick={change}>
|
<div className="admin-inline-actions">
|
||||||
修改密码
|
<Button variant="secondary" loading={busy} icon={<Icon name="key_2_line" />} onClick={change}>
|
||||||
</Button>
|
修改密码
|
||||||
<Button icon={<Icon name="download_line" />} onClick={() => window.location.assign('/api/backup')}>
|
</Button>
|
||||||
下载备份
|
</div>
|
||||||
</Button>
|
|
||||||
|
<div className="admin-form-divider" />
|
||||||
|
|
||||||
|
<Text size="sm" strong>
|
||||||
|
数据备份
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" tone="muted">
|
||||||
|
下载当前数据库快照(VACUUM INTO),不会中断服务。
|
||||||
|
</Text>
|
||||||
|
<div className="admin-inline-actions">
|
||||||
|
<Button icon={<Icon name="download_line" />} onClick={() => window.location.assign('/api/backup')}>
|
||||||
|
下载备份
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user