fix(admin): 内容下拉去格式后缀,侧边栏开关右置并即时保存
- 内容页/条目弹窗的分组下拉只显示名称,去掉格式后缀与缩进,统一对齐 - 侧边栏开关移到右侧;拨动即保存到 settings.sidebar.tree_hidden - 移除侧边栏页的保存/撤销按钮 - Playwright 验证:选项无括号且对齐、开关在右、无保存按钮、设置即时落库
This commit is contained in:
+1
-1
@@ -99,7 +99,7 @@ README.md 运行、路由、库结构、登录说明
|
||||
| --- | --- |
|
||||
| 分组管理 | 树形表格:新建根分组(左侧)、加子分组、编辑(名称/上级/格式/图标/排序/状态)、删除;行内状态开关即时生效;图标可留空 |
|
||||
| 内容 | 选分组以列表展示条目(选主分组时包含所有子分组);列:标题 / 跳转链接 / 所属分组 / 操作;弹窗新增/编辑,编辑时显示不可改的「标识」 |
|
||||
| 侧边栏 | 分组树逐项勾选(`settings.sidebar.tree_hidden`),子分组可单独勾选 |
|
||||
| 侧边栏 | 分组树逐项勾选,开关在右侧、**拨动即保存**(无单独保存/撤销按钮);子分组可单独勾选 |
|
||||
| 首页卡片 | 新建/改名/删除卡片;勾选若干分组(可混合格式),每组单独设条数 |
|
||||
| 页头 | 启用开关、速度、条目、标签与色调 |
|
||||
| 资料 | 头像 URL、名称、角色、简介、位置 |
|
||||
|
||||
@@ -147,6 +147,12 @@ DELETE /api/items/:id
|
||||
|
||||
## 11. 实施记录
|
||||
|
||||
### P12(2026-10-31,内容/侧边栏交互微调)
|
||||
|
||||
- 内容页与条目弹窗的分组下拉只显示分组名,去掉格式后缀与缩进空格,选项统一左对齐;
|
||||
- 侧边栏页:开关移到右侧,拨动即写入 `settings.sidebar.tree_hidden`;移除「保存侧边栏 / 撤销改动 / 保存」按钮,保存成功用 Toast 提示;
|
||||
- 验证:下拉选项无括号、x 对齐;侧边栏无保存按钮、开关在右、切换后设置即时落库。
|
||||
|
||||
### P11(2026-10-31,内容页列表化)
|
||||
|
||||
- 内容页改为与分组管理一致的列表:标题 / 跳转链接 / 所属分组 / 操作;
|
||||
|
||||
@@ -1267,6 +1267,20 @@ a.entry-href:hover {
|
||||
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;
|
||||
}
|
||||
|
||||
.admin-category-row > * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@@ -136,6 +136,22 @@ export function AdminNext() {
|
||||
toast.success('已保存')
|
||||
}
|
||||
|
||||
/** 侧边栏开关即时保存(不再单独点保存按钮) */
|
||||
const saveSidebar = async (sidebar: SiteSettings['sidebar']) => {
|
||||
if (document === null || draft === null) return
|
||||
setSaving(true)
|
||||
const result = await saveSettings(document.version, { ...draft, sidebar })
|
||||
setSaving(false)
|
||||
if (!result.ok) {
|
||||
toast.error(result.error)
|
||||
void load()
|
||||
return
|
||||
}
|
||||
setDocument(result.document)
|
||||
setDraft(clone(result.document.settings))
|
||||
toast.success('侧边栏已保存')
|
||||
}
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (pending === null) return
|
||||
setDeleting(true)
|
||||
@@ -234,9 +250,13 @@ export function AdminNext() {
|
||||
<SidebarPanel
|
||||
groups={groups}
|
||||
value={draft.sidebar}
|
||||
onChange={(sidebar) => setDraft({ ...draft, sidebar })}
|
||||
onSave={save}
|
||||
saving={saving}
|
||||
onToggle={(key, visible) => {
|
||||
const hidden = new Set(draft.sidebar.tree_hidden)
|
||||
if (visible) hidden.delete(key)
|
||||
else hidden.add(key)
|
||||
void saveSidebar({ ...draft.sidebar, tree_hidden: [...hidden] })
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -265,7 +285,7 @@ export function AdminNext() {
|
||||
|
||||
{tab === 'system' && <SystemPanel />}
|
||||
|
||||
{(tab === 'header' || tab === 'profile' || tab === 'sidebar') && (
|
||||
{(tab === 'header' || tab === 'profile') && (
|
||||
<Space gap="sm" justify="end" className="admin-actions">
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -833,7 +853,7 @@ function ContentPanel({ groups, onReload }: { groups: GroupNode[]; onReload: ()
|
||||
<Select
|
||||
options={options.map((option) => ({
|
||||
value: option.key,
|
||||
label: `${' '.repeat(option.depth)}${option.title}(${FORMAT_LABEL[option.format]})`,
|
||||
label: option.title,
|
||||
}))}
|
||||
value={group?.key ?? ''}
|
||||
onChange={setGroupKey}
|
||||
@@ -1084,7 +1104,7 @@ function EntryDialog({
|
||||
<Select
|
||||
options={options.map((option) => ({
|
||||
value: option.key,
|
||||
label: `${' '.repeat(option.depth)}${option.title}`,
|
||||
label: option.title,
|
||||
}))}
|
||||
value={groupKey}
|
||||
onChange={setGroupKey}
|
||||
@@ -1169,38 +1189,27 @@ function EntryDialog({
|
||||
function SidebarPanel({
|
||||
groups,
|
||||
value,
|
||||
onChange,
|
||||
onSave,
|
||||
onToggle,
|
||||
saving,
|
||||
}: {
|
||||
groups: GroupNode[]
|
||||
value: SiteSettings['sidebar']
|
||||
onChange: (sidebar: SiteSettings['sidebar']) => void
|
||||
onSave: () => void
|
||||
onToggle: (key: string, visible: boolean) => void
|
||||
saving: boolean
|
||||
}) {
|
||||
const hidden = new Set(value.tree_hidden)
|
||||
|
||||
const toggle = (key: string, visible: boolean) => {
|
||||
const next = new Set(hidden)
|
||||
if (visible) next.delete(key)
|
||||
else next.add(key)
|
||||
onChange({ ...value, tree_hidden: [...next] })
|
||||
}
|
||||
|
||||
function render(nodes: GroupNode[], depth: number) {
|
||||
return nodes.map((node) => (
|
||||
<div key={node.key} style={{ marginLeft: depth * 22 }}>
|
||||
<div className="admin-item-row admin-nav-row">
|
||||
<div className="sidebar-toggle-row">
|
||||
<Text className="admin-item-title">{node.title}</Text>
|
||||
<Switch
|
||||
checked={!hidden.has(node.key)}
|
||||
onChange={(visible) => toggle(node.key, visible)}
|
||||
onChange={(visible) => onToggle(node.key, visible)}
|
||||
label={`侧栏显示 ${node.title}`}
|
||||
disabled={saving}
|
||||
/>
|
||||
<Text className="admin-item-title">{node.title}</Text>
|
||||
<Text size="sm" tone="muted">
|
||||
{node.format}
|
||||
</Text>
|
||||
</div>
|
||||
{render(node.children, depth + 1)}
|
||||
</div>
|
||||
@@ -1210,14 +1219,9 @@ function SidebarPanel({
|
||||
return (
|
||||
<Space direction="column" gap="md" align="stretch" className="admin-form">
|
||||
<Text size="sm" tone="muted">
|
||||
勾选要在侧边栏显示的分组;子分组可以单独勾选,父分组不勾选也不影响子项。
|
||||
勾选要在侧边栏显示的分组;子分组可以单独勾选,父分组不勾选也不影响子项。开关修改后立即保存。
|
||||
</Text>
|
||||
{render(groups, 0)}
|
||||
<Space gap="sm" justify="end">
|
||||
<Button loading={saving} icon={<Icon name="check_line" />} onClick={onSave}>
|
||||
保存侧边栏
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user