refactor(admin): 三张手写表格改用库里的 Table,文字按钮/开关行收进库

- 分组管理 / 数据管理 / 内容卡片 / 本地服务 四处手写 <table> 全部换成 Table size='sm'
- 行内操作(新建子分组 / 编辑 / 删除)改用 TextButton,删除统一 danger 色
- 侧边栏、页头共 7 处开关行改用 ToggleRow
- 空状态改用 Empty
- 删掉随之失效的 CSS:.group-table* / .entry-table* / .cards-table* / .group-action* /
  .sidebar-toggle-row / .admin-item-title 等,App.css 净减 144 行

视觉:表格外框、表头底色、行 hover 统一由 @stash/ui 提供,与接口页一致
This commit is contained in:
2026-11-01 21:59:18 +08:00
parent a67ab7bfe0
commit 4ae69b29fe
2 changed files with 293 additions and 515 deletions
+23 -167
View File
@@ -795,15 +795,7 @@
gap: var(--su-space-2); gap: var(--su-space-2);
} }
.admin-item-row > :first-child:not(.admin-item-title) {
grid-column: span 1;
}
.admin-item-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.admin-nav-row { .admin-nav-row {
grid-template-columns: auto minmax(0, 1fr) auto; grid-template-columns: auto minmax(0, 1fr) auto;
@@ -927,10 +919,6 @@
padding-left: var(--su-space-6); padding-left: var(--su-space-6);
} }
.admin-nav-group-row .admin-item-title {
color: var(--su-text-sub);
font-size: var(--su-font-size-sm);
}
/* 归类页:一行一个分区,标题 / 类型 / 图标 / 色条 / 条数 / 保存 / 删除 */ /* 归类页:一行一个分区,标题 / 类型 / 图标 / 色条 / 条数 / 保存 / 删除 */
.admin-category-row { .admin-category-row {
@@ -977,62 +965,15 @@
justify-content: flex-start; justify-content: flex-start;
} }
.group-table-wrap {
overflow: hidden;
border: 1px solid var(--su-card-border);
border-radius: var(--su-radius-md);
background-color: var(--su-bg-card);
}
.group-table {
width: 100%;
border-collapse: collapse;
font-size: var(--su-font-size-md);
}
.group-table th {
padding: var(--su-space-3) var(--su-space-4);
background-color: var(--su-bg-subtle);
border-bottom: 1px solid var(--su-card-border);
color: var(--su-text-muted);
font-size: var(--su-font-size-sm);
font-weight: 600;
text-align: left;
white-space: nowrap;
}
.group-table td {
padding: var(--su-space-3) var(--su-space-4);
border-bottom: 1px solid var(--su-card-border);
vertical-align: middle;
}
.group-table tbody tr:last-child td {
border-bottom: none;
}
.group-table tbody tr:hover {
background-color: var(--su-bg-subtle);
}
.group-col-sort {
width: 110px;
}
.group-col-count {
width: 120px;
text-align: center !important;
}
.group-col-status {
width: 90px;
text-align: center !important;
}
.group-col-actions {
width: 190px;
text-align: right !important;
}
.group-name-cell { .group-name-cell {
display: flex; display: flex;
@@ -1183,49 +1124,12 @@
font-size: var(--su-font-size-sm); font-size: var(--su-font-size-sm);
} }
.group-sort {
color: var(--su-text-muted);
font-family: var(--su-font-mono);
font-size: var(--su-font-size-sm);
}
.group-count {
color: var(--su-text-sub);
text-align: center;
}
.group-actions {
display: flex;
justify-content: flex-end;
gap: var(--su-space-3);
}
.group-action {
display: inline-flex;
align-items: center;
gap: var(--su-space-1);
padding: 0;
border: none;
background: none;
color: var(--su-color-primary);
font-size: var(--su-font-size-sm);
cursor: pointer;
white-space: nowrap;
}
.group-action:hover {
text-decoration: underline;
}
.group-action-danger {
color: var(--su-color-danger);
}
.group-empty {
padding: var(--su-space-6) 0 !important;
color: var(--su-text-muted);
text-align: center;
}
.group-icon-field { .group-icon-field {
display: flex; display: flex;
@@ -1251,14 +1155,7 @@
gap: var(--su-space-3); gap: var(--su-space-3);
} }
/* 内容页:与分组管理一致的列表 */
.entry-table .entry-col-href {
width: 36%;
}
.entry-table .entry-col-format {
width: 80px;
}
.entry-format { .entry-format {
display: inline-block; display: inline-block;
@@ -1269,9 +1166,6 @@
font-size: var(--su-font-size-sm); font-size: var(--su-font-size-sm);
} }
.entry-table .entry-col-group {
width: 180px;
}
.entry-title { .entry-title {
color: var(--su-text-main); color: var(--su-text-main);
@@ -1303,19 +1197,7 @@ a.entry-href:hover {
font-size: var(--su-font-size-sm); font-size: var(--su-font-size-sm);
} }
/* 侧边栏勾选:标题在左,开关在右 */
.sidebar-toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--su-space-3);
padding: var(--su-space-1) 0;
}
.sidebar-toggle-row .admin-item-title {
flex: 1;
min-width: 0;
}
/* 首页:左右栏共用一个滚动区,左侧吸顶交给 CSS sticky */ /* 首页:左右栏共用一个滚动区,左侧吸顶交给 CSS sticky */
#page-scroll { #page-scroll {
@@ -1500,44 +1382,12 @@ a.entry-href:hover {
min-width: 0; min-width: 0;
} }
.cards-table-head {
display: flex;
align-items: center;
gap: var(--su-space-2);
margin-bottom: var(--su-space-2);
}
.cards-table-wrap {
margin-top: var(--su-space-2);
/* 列多了以后表格会比面板宽,横向滚动而不是把名字挤成两三行 */
overflow-x: auto;
}
.cards-table th,
.cards-table td {
padding: var(--su-space-2);
}
.cards-col-limit {
width: 100px;
}
/* 分组名不换行,超长省略;列宽固定,保证五列都落在面板内 */
.cards-table {
table-layout: fixed;
}
.cards-table td:first-child .group-name {
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.cards-table th,
.cards-table td {
padding: var(--su-space-2) var(--su-space-4);
}
/* 首页 Tab:侧边栏设置 + 首页卡片上下两段 */ /* 首页 Tab:侧边栏设置 + 首页卡片上下两段 */
.admin-home { .admin-home {
@@ -1552,14 +1402,7 @@ a.entry-href:hover {
gap: var(--su-space-3); gap: var(--su-space-3);
} }
/* 首页卡片:分组展示顺序(表格里的「顺序」列,上下箭头即时保存) */
.cards-col-state {
width: 72px;
}
.cards-col-order {
width: 112px;
}
.card-order-cell { .card-order-cell {
display: flex; display: flex;
@@ -1682,17 +1525,7 @@ a.entry-href:hover {
border-bottom: 1px solid var(--su-border); border-bottom: 1px solid var(--su-border);
} }
/* 删除按钮:可删的用危险色,不可删的用弱化色(都取主题令牌) */
.admin-item-actions .admin-remove,
.cards-table-head .admin-remove {
color: var(--su-color-danger);
}
.admin-item-actions .admin-remove:not(:disabled):hover,
.cards-table-head .admin-remove:not(:disabled):hover {
color: var(--su-color-danger);
background-color: color-mix(in srgb, var(--su-color-danger) 10%, transparent);
}
.admin-item-actions .admin-remove:disabled { .admin-item-actions .admin-remove:disabled {
color: var(--su-text-muted); color: var(--su-text-muted);
@@ -1700,3 +1533,26 @@ a.entry-href:hover {
opacity: 0.45; opacity: 0.45;
} }
/* 后台表格里的操作列与卡片标题栏(其余表格样式已交给 @stash/ui 的 Table) */
.group-actions {
display: flex;
justify-content: flex-end;
gap: var(--su-space-3);
}
.cards-table-head {
display: flex;
align-items: center;
gap: var(--su-space-2);
margin-bottom: var(--su-space-2);
}
.cards-table-head .admin-remove {
color: var(--su-color-danger);
}
.cards-table-head .admin-remove:not(:disabled):hover {
color: var(--su-color-danger);
background-color: color-mix(in srgb, var(--su-color-danger) 10%, transparent);
}
+270 -348
View File
@@ -2,6 +2,7 @@ import {
Brand, Brand,
Button, Button,
Card, Card,
Empty,
FormField, FormField,
Icon, Icon,
ICON_NAMES, ICON_NAMES,
@@ -14,11 +15,14 @@ import {
Select, Select,
Space, Space,
Switch, Switch,
Table,
Tabs, Tabs,
Tag, Tag,
Text, Text,
Textarea, Textarea,
TextButton,
Title, Title,
ToggleRow,
Toaster, Toaster,
toast, toast,
type IconName, type IconName,
@@ -578,92 +582,75 @@ function GroupsPanel({
</Button> </Button>
</div> </div>
<div className="group-table-wrap"> <Table
<table className="group-table"> size="sm"
<thead> rowKey="key"
<tr> columns={[
<th>分组名称</th> { key: 'name', title: '分组名称' },
<th className="group-col-sort">排序权重</th> { key: 'sort', title: '排序权重', width: 110 },
<th className="group-col-count">关联网页数</th> { key: 'count', title: '关联网页数', width: 120 },
<th className="group-col-status">状态</th> { key: 'status', title: '状态', width: 90 },
<th className="group-col-actions">操作</th> { key: 'actions', title: '操作', width: 260, align: 'right' },
</tr> ]}
</thead> rows={rows.map(({ node, level }) => {
<tbody> const hasChildren = node.children.length > 0
{rows.map(({ node, level }) => { const open = !collapsed.has(node.key)
const hasChildren = node.children.length > 0 return {
const open = !collapsed.has(node.key) key: node.key,
return ( name: (
<tr key={node.key}> <div className="group-name-cell" style={{ paddingLeft: level * 20 }}>
<td> {hasChildren ? (
<div className="group-name-cell" style={{ paddingLeft: level * 20 }}> <button
{hasChildren ? ( type="button"
<button className={`group-caret${open ? ' group-caret-open' : ''}`}
type="button" aria-label={open ? `折叠 ${node.title}` : `展开 ${node.title}`}
className={`group-caret${open ? ' group-caret-open' : ''}`} aria-expanded={open}
aria-label={open ? `折叠 ${node.title}` : `展开 ${node.title}`} onClick={() => toggle(node.key)}
aria-expanded={open} />
onClick={() => toggle(node.key)} ) : (
/> <span className="group-caret group-caret-empty" aria-hidden="true" />
) : ( )}
<span className="group-caret group-caret-empty" aria-hidden="true" /> <GroupMark icon={node.icon} />
)} <span className="group-name">{node.title}</span>
<GroupMark icon={node.icon} /> </div>
<span className="group-name">{node.title}</span> ),
</div> sort: node.sort,
</td> count: countGroupEntries(node),
<td className="group-sort">{node.sort}</td> status: (
<td className="group-count">{countGroupEntries(node)}</td> <Switch
<td> checked={node.enabled !== false}
<Switch onChange={(next) => void toggleEnabled(node, next)}
checked={node.enabled !== false} label={`启用 ${node.title}`}
onChange={(next) => void toggleEnabled(node, next)} size="sm"
label={`启用 ${node.title}`} />
size="sm" ),
/> actions: (
</td> <div className="group-actions">
<td> <TextButton
<div className="group-actions"> icon={<Icon name="add_line" />}
<button onClick={() => setDialog({ mode: 'child', parent: node })}
type="button" >
className="group-action" 新建子分组
onClick={() => setDialog({ mode: 'child', parent: node })} </TextButton>
> <TextButton
<Icon name="add_line" /> icon={<Icon name="edit_4_line" />}
新建子分组 onClick={() => setDialog({ mode: 'edit', group: node })}
</button> >
<button 编辑
type="button" </TextButton>
className="group-action" <TextButton
onClick={() => setDialog({ mode: 'edit', group: node })} tone="danger"
> icon={<Icon name="delete_2_line" />}
<Icon name="edit_4_line" /> onClick={() => onDelete(node.key, node.title)}
编辑 >
</button> 删除
<button </TextButton>
type="button" </div>
className="group-action group-action-danger" ),
onClick={() => onDelete(node.key, node.title)} }
> })}
<Icon name="delete_2_line" /> emptyText={<Empty text="暂无分组数据,点击上方「新建根分组」" />}
删除 />
</button>
</div>
</td>
</tr>
)
})}
{rows.length === 0 && (
<tr>
<td colSpan={5} className="group-empty">
暂无分组数据,点击左侧「新建根分组」
</td>
</tr>
)}
</tbody>
</table>
</div>
<GroupDialog <GroupDialog
state={dialog} state={dialog}
@@ -1019,75 +1006,54 @@ function ContentPanel({ groups, onReload }: { groups: GroupNode[]; onReload: ()
{group?.title ?? ''} 下共 {rows.length} 条(含子分组) {group?.title ?? ''} 下共 {rows.length} 条(含子分组)
</Text> </Text>
<div className="group-table-wrap"> <Table
<table className="group-table entry-table"> size="sm"
<thead> rowKey="key"
<tr> columns={[
<th>标题</th> { key: 'title', title: '标题' },
<th className="entry-col-format">格式</th> { key: 'format', title: '格式', width: 80 },
<th className="entry-col-href">跳转链接</th> { key: 'href', title: '跳转链接', width: '36%' },
<th className="entry-col-group">所属分组</th> { key: 'group', title: '所属分组', width: 180 },
<th className="group-col-actions">操作</th> { key: 'actions', title: '操作', width: 160, align: 'right' },
</tr> ]}
</thead> rows={rows.map(({ entry, group: owner }) => ({
<tbody> key: entry.id,
{rows.map(({ entry, group: owner }) => ( title: <span className="entry-title">{entry.name ?? entry.title ?? entry.id}</span>,
<tr key={entry.id}> format: (
<td> <span className="entry-format">
<span className="entry-title">{entry.name ?? entry.title ?? entry.id}</span> {FORMAT_LABEL[(entry.format ?? 'apps') as GroupFormat]}
</td> </span>
<td> ),
<span className="entry-format"> href: <EntryLink href={entry.href} />,
{FORMAT_LABEL[(entry.format ?? 'apps') as GroupFormat]} group: <span className="entry-group">{owner.title}</span>,
</span> actions: (
</td> <div className="group-actions">
<td> <TextButton
<EntryLink href={entry.href} /> icon={<Icon name="edit_4_line" />}
</td> onClick={() => setDialog({ key: entry.id, groupKey: owner.key })}
<td> >
<span className="entry-group">{owner.title}</span> 编辑
</td> </TextButton>
<td> <TextButton
<div className="group-actions"> tone="danger"
<button icon={<Icon name="delete_2_line" />}
type="button" onClick={async () => {
className="group-action" const result = await deleteItem(entry.id)
onClick={() => setDialog({ key: entry.id, groupKey: owner.key })} if (!result.ok) {
> toast.error(result.error)
<Icon name="edit_4_line" /> return
编辑 }
</button> toast.success('条目已删除')
<button onReload()
type="button" }}
className="group-action group-action-danger" >
onClick={async () => { 删除
const result = await deleteItem(entry.id) </TextButton>
if (!result.ok) { </div>
toast.error(result.error) ),
return }))}
} emptyText={<Empty text="该分组下还没有内容" />}
toast.success('条目已删除') />
onReload()
}}
>
<Icon name="delete_2_line" />
删除
</button>
</div>
</td>
</tr>
))}
{rows.length === 0 && (
<tr>
<td colSpan={5} className="group-empty">
该分组下还没有内容
</td>
</tr>
)}
</tbody>
</table>
</div>
<EntryDialog <EntryDialog
open={dialog !== null} open={dialog !== null}
@@ -1371,33 +1337,27 @@ function SidebarPanel({
<Text size="sm" strong> <Text size="sm" strong>
侧栏卡片 侧栏卡片
</Text> </Text>
<div className="sidebar-toggle-row"> <ToggleRow
<Text className="admin-item-title">本地管理员</Text> label="本地管理员"
<Switch checked={value.show_profile !== false}
checked={value.show_profile !== false} onChange={(visible) => onToggleCard('profile', visible)}
onChange={(visible) => onToggleCard('profile', visible)} switchLabel="显示本地管理员卡片"
label="显示本地管理员卡片" disabled={saving}
disabled={saving} />
/> <ToggleRow
</div> label="本地服务"
<div className="sidebar-toggle-row"> checked={value.show_service !== false}
<Text className="admin-item-title">本地服务</Text> onChange={(visible) => onToggleCard('service', visible)}
<Switch switchLabel="显示本地服务卡片"
checked={value.show_service !== false} disabled={saving}
onChange={(visible) => onToggleCard('service', visible)} />
label="显示本地服务卡片" <ToggleRow
disabled={saving} label="分组导航"
/> checked={value.show_nav !== false}
</div> onChange={(visible) => onToggleCard('nav', visible)}
<div className="sidebar-toggle-row"> switchLabel="显示分组导航卡片"
<Text className="admin-item-title">分组导航</Text> disabled={saving}
<Switch />
checked={value.show_nav !== false}
onChange={(visible) => onToggleCard('nav', visible)}
label="显示分组导航卡片"
disabled={saving}
/>
</div>
</Space> </Space>
) )
} }
@@ -1634,42 +1594,23 @@ function LocalServiceTable({ projects }: { projects: Project[] }) {
本地服务卡片由托管目录自动生成,随项目扫描更新;后台只能查看,不能删除或改名。 本地服务卡片由托管目录自动生成,随项目扫描更新;后台只能查看,不能删除或改名。
</Text> </Text>
<div className="group-table-wrap cards-table-wrap"> <Table
<table className="group-table cards-table"> size="sm"
<thead> rowKey="name"
<tr> columns={[
<th>项目名称</th> { key: 'name', title: '项目名称' },
<th className="entry-col-href">访问地址</th> { key: 'href', title: '访问地址', width: '50%' },
</tr> ]}
</thead> rows={projects.map((project) => ({
<tbody> name: <span className="group-name">{project.name}</span>,
{projects.map((project) => ( href: (
<tr key={project.name}> <a className="entry-href" href={project.route_path} target="_blank" rel="noreferrer">
<td> {project.route_path}
<span className="group-name">{project.name}</span> </a>
</td> ),
<td> }))}
<a emptyText={<Empty text="还没有托管项目" />}
className="entry-href" />
href={project.route_path}
target="_blank"
rel="noreferrer"
>
{project.route_path}
</a>
</td>
</tr>
))}
{projects.length === 0 && (
<tr>
<td colSpan={2} className="group-empty">
还没有托管项目
</td>
</tr>
)}
</tbody>
</table>
</div>
</div> </div>
) )
} }
@@ -1795,88 +1736,80 @@ function CardTable({
说明:开关和条数修改后即时保存;条数留空使用默认。 说明:开关和条数修改后即时保存;条数留空使用默认。
</Text> </Text>
<div className="group-table-wrap cards-table-wrap"> <Table
<table className="group-table cards-table"> size="sm"
<thead> rowKey="key"
<tr> columns={[
<th>分组名称</th> { key: 'name', title: '分组名称' },
<th className="cards-col-state">状态</th> { key: 'state', title: '状态', width: 90 },
<th className="cards-col-limit">预览条数</th> { key: 'limit', title: '预览条数', width: 110 },
<th className="cards-col-order">顺序</th> { key: 'order', title: '顺序', width: 120 },
<th className="group-col-status">展示</th> { key: 'show', title: '展示', width: 90 },
</tr> ]}
</thead> rows={options.map((option) => {
<tbody> const checked = option.key in selected
{options.map((option) => { return {
const checked = option.key in selected key: option.key,
return ( name: (
<tr key={option.key}> <div style={{ paddingLeft: option.depth * 18 }}>
<td> <span className="group-name">{option.title}</span>
<div style={{ paddingLeft: option.depth * 18 }}> </div>
<span className="group-name">{option.title}</span> ),
</div> state: (
</td> <span className={option.enabled ? 'card-state card-state-on' : 'card-state card-state-off'}>
<td> {option.enabled ? '启用' : '已停用'}
<span className={option.enabled ? 'card-state card-state-on' : 'card-state card-state-off'}> </span>
{option.enabled ? '启用' : '已停用'} ),
</span> limit: checked ? (
</td> <Input
<td> type="number"
{checked ? ( value={selected[option.key]}
<Input placeholder="默认"
type="number" aria-label={`${option.title} 条数`}
value={selected[option.key]} onChange={(event) =>
placeholder="默认" setSelected((current) => ({
aria-label={`${option.title} 条数`} ...current,
onChange={(event) => [option.key]: event.target.value,
setSelected((current) => ({ }))
...current, }
[option.key]: event.target.value, onBlur={() => void persistGroups(selected, order)}
})) />
} ) : (
onBlur={() => void persistGroups(selected, order)} <span className="entry-href entry-href-empty">—</span>
/> ),
) : ( order: checked ? (
<span className="entry-href entry-href-empty">—</span> <div className="card-order-cell">
)} <span className="card-order-index">{order.indexOf(option.key) + 1}</span>
</td> <IconButton
<td> label={`${option.title} 上移`}
{checked ? ( icon={<Icon name="arrow_up_line" />}
<div className="card-order-cell"> size="xs"
<span className="card-order-index">{order.indexOf(option.key) + 1}</span> disabled={busy || order.indexOf(option.key) === 0}
<IconButton onClick={() => moveGroup(option.key, -1)}
label={`${option.title} 上移`} />
icon={<Icon name="arrow_up_line" />} <IconButton
size="xs" label={`${option.title} 下移`}
disabled={busy || order.indexOf(option.key) === 0} icon={<Icon name="arrow_down_line" />}
onClick={() => moveGroup(option.key, -1)} size="xs"
/> disabled={busy || order.indexOf(option.key) === order.length - 1}
<IconButton onClick={() => moveGroup(option.key, 1)}
label={`${option.title} 下移`} />
icon={<Icon name="arrow_down_line" />} </div>
size="xs" ) : (
disabled={busy || order.indexOf(option.key) === order.length - 1} <span className="entry-href entry-href-empty">—</span>
onClick={() => moveGroup(option.key, 1)} ),
/> show: (
</div> <Switch
) : ( checked={checked}
<span className="entry-href entry-href-empty">—</span> onChange={(next) => toggle(option.key, next)}
)} label={`卡片包含 ${option.title}`}
</td> disabled={busy || !option.enabled}
<td> />
<Switch ),
checked={checked} }
onChange={(next) => toggle(option.key, next)} })}
label={`卡片包含 ${option.title}`} emptyText={<Empty text="还没有可选的分组" />}
disabled={busy || !option.enabled} />
/>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div> </div>
) )
} }
@@ -1993,59 +1926,48 @@ function HeaderPanel({
<Text size="sm" tone="muted"> <Text size="sm" tone="muted">
说明:这里控制页头右侧显示哪些图标按钮;关掉的按钮不再出现,也不影响其它页面。 说明:这里控制页头右侧显示哪些图标按钮;关掉的按钮不再出现,也不影响其它页面。
</Text> </Text>
<div className="sidebar-toggle-row"> <ToggleRow
<Text>明暗切换</Text> label="明暗切换"
<Switch checked={site.actions.theme}
checked={site.actions.theme} onChange={(next) =>
onChange={(next) => onChangeSite({ ...site, actions: { ...site.actions, theme: next } })
onChangeSite({ ...site, actions: { ...site.actions, theme: next } }) }
} switchLabel="页头显示明暗切换按钮"
label="页头显示明暗切换按钮" />
/> <ToggleRow
</div> label="刷新"
<div className="sidebar-toggle-row"> checked={site.actions.refresh}
<Text>刷新</Text> onChange={(next) =>
<Switch onChangeSite({ ...site, actions: { ...site.actions, refresh: next } })
checked={site.actions.refresh} }
onChange={(next) => switchLabel="页头显示刷新按钮"
onChangeSite({ ...site, actions: { ...site.actions, refresh: next } }) />
} <ToggleRow
label="页头显示刷新按钮" label="站点设置"
/> checked={site.actions.settings}
</div> onChange={(next) =>
<div className="sidebar-toggle-row"> onChangeSite({ ...site, actions: { ...site.actions, settings: next } })
<Text>站点设置</Text> }
<Switch switchLabel="页头显示站点设置按钮"
checked={site.actions.settings} />
onChange={(next) => <ToggleRow
onChangeSite({ ...site, actions: { ...site.actions, settings: next } }) label="登录 / 退出"
} checked={site.actions.session}
label="页头显示站点设置按钮" onChange={(next) =>
/> onChangeSite({ ...site, actions: { ...site.actions, session: next } })
</div> }
<div className="sidebar-toggle-row"> switchLabel="页头显示登录或退出按钮"
<Text>登录 / 退出</Text> />
<Switch
checked={site.actions.session}
onChange={(next) =>
onChangeSite({ ...site, actions: { ...site.actions, session: next } })
}
label="页头显示登录或退出按钮"
/>
</div>
</Space> </Space>
)} )}
{tab === 'ticker' && ( {tab === 'ticker' && (
<Space direction="column" gap="md" align="stretch"> <Space direction="column" gap="md" align="stretch">
<div className="sidebar-toggle-row"> <ToggleRow
<Text>显示页头滚动信息</Text> label="显示页头滚动信息"
<Switch checked={value.enabled}
checked={value.enabled} onChange={(enabled) => onChange({ ...value, enabled })}
onChange={(enabled) => onChange({ ...value, enabled })} />
label="显示页头滚动信息"
/>
</div>
<FormField label="滚动速度" htmlFor="header-speed" hint="秒数越大越慢,范围 8–120"> <FormField label="滚动速度" htmlFor="header-speed" hint="秒数越大越慢,范围 8–120">
<Input <Input
id="header-speed" id="header-speed"