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、名称、角色、简介、位置 |
|
| 资料 | 头像 URL、名称、角色、简介、位置 |
|
||||||
|
|||||||
@@ -147,6 +147,12 @@ DELETE /api/items/:id
|
|||||||
|
|
||||||
## 11. 实施记录
|
## 11. 实施记录
|
||||||
|
|
||||||
|
### P12(2026-10-31,内容/侧边栏交互微调)
|
||||||
|
|
||||||
|
- 内容页与条目弹窗的分组下拉只显示分组名,去掉格式后缀与缩进空格,选项统一左对齐;
|
||||||
|
- 侧边栏页:开关移到右侧,拨动即写入 `settings.sidebar.tree_hidden`;移除「保存侧边栏 / 撤销改动 / 保存」按钮,保存成功用 Toast 提示;
|
||||||
|
- 验证:下拉选项无括号、x 对齐;侧边栏无保存按钮、开关在右、切换后设置即时落库。
|
||||||
|
|
||||||
### P11(2026-10-31,内容页列表化)
|
### P11(2026-10-31,内容页列表化)
|
||||||
|
|
||||||
- 内容页改为与分组管理一致的列表:标题 / 跳转链接 / 所属分组 / 操作;
|
- 内容页改为与分组管理一致的列表:标题 / 跳转链接 / 所属分组 / 操作;
|
||||||
|
|||||||
@@ -1267,6 +1267,20 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
.admin-category-row > * {
|
.admin-category-row > * {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,22 @@ export function AdminNext() {
|
|||||||
toast.success('已保存')
|
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 () => {
|
const confirmDelete = async () => {
|
||||||
if (pending === null) return
|
if (pending === null) return
|
||||||
setDeleting(true)
|
setDeleting(true)
|
||||||
@@ -234,9 +250,13 @@ export function AdminNext() {
|
|||||||
<SidebarPanel
|
<SidebarPanel
|
||||||
groups={groups}
|
groups={groups}
|
||||||
value={draft.sidebar}
|
value={draft.sidebar}
|
||||||
onChange={(sidebar) => setDraft({ ...draft, sidebar })}
|
|
||||||
onSave={save}
|
|
||||||
saving={saving}
|
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 === 'system' && <SystemPanel />}
|
||||||
|
|
||||||
{(tab === 'header' || tab === 'profile' || tab === 'sidebar') && (
|
{(tab === 'header' || tab === 'profile') && (
|
||||||
<Space gap="sm" justify="end" className="admin-actions">
|
<Space gap="sm" justify="end" className="admin-actions">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -833,7 +853,7 @@ function ContentPanel({ groups, onReload }: { groups: GroupNode[]; onReload: ()
|
|||||||
<Select
|
<Select
|
||||||
options={options.map((option) => ({
|
options={options.map((option) => ({
|
||||||
value: option.key,
|
value: option.key,
|
||||||
label: `${' '.repeat(option.depth)}${option.title}(${FORMAT_LABEL[option.format]})`,
|
label: option.title,
|
||||||
}))}
|
}))}
|
||||||
value={group?.key ?? ''}
|
value={group?.key ?? ''}
|
||||||
onChange={setGroupKey}
|
onChange={setGroupKey}
|
||||||
@@ -1084,7 +1104,7 @@ function EntryDialog({
|
|||||||
<Select
|
<Select
|
||||||
options={options.map((option) => ({
|
options={options.map((option) => ({
|
||||||
value: option.key,
|
value: option.key,
|
||||||
label: `${' '.repeat(option.depth)}${option.title}`,
|
label: option.title,
|
||||||
}))}
|
}))}
|
||||||
value={groupKey}
|
value={groupKey}
|
||||||
onChange={setGroupKey}
|
onChange={setGroupKey}
|
||||||
@@ -1169,38 +1189,27 @@ function EntryDialog({
|
|||||||
function SidebarPanel({
|
function SidebarPanel({
|
||||||
groups,
|
groups,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onToggle,
|
||||||
onSave,
|
|
||||||
saving,
|
saving,
|
||||||
}: {
|
}: {
|
||||||
groups: GroupNode[]
|
groups: GroupNode[]
|
||||||
value: SiteSettings['sidebar']
|
value: SiteSettings['sidebar']
|
||||||
onChange: (sidebar: SiteSettings['sidebar']) => void
|
onToggle: (key: string, visible: boolean) => void
|
||||||
onSave: () => void
|
|
||||||
saving: boolean
|
saving: boolean
|
||||||
}) {
|
}) {
|
||||||
const hidden = new Set(value.tree_hidden)
|
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) {
|
function render(nodes: GroupNode[], depth: number) {
|
||||||
return nodes.map((node) => (
|
return nodes.map((node) => (
|
||||||
<div key={node.key} style={{ marginLeft: depth * 22 }}>
|
<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
|
<Switch
|
||||||
checked={!hidden.has(node.key)}
|
checked={!hidden.has(node.key)}
|
||||||
onChange={(visible) => toggle(node.key, visible)}
|
onChange={(visible) => onToggle(node.key, visible)}
|
||||||
label={`侧栏显示 ${node.title}`}
|
label={`侧栏显示 ${node.title}`}
|
||||||
|
disabled={saving}
|
||||||
/>
|
/>
|
||||||
<Text className="admin-item-title">{node.title}</Text>
|
|
||||||
<Text size="sm" tone="muted">
|
|
||||||
{node.format}
|
|
||||||
</Text>
|
|
||||||
</div>
|
</div>
|
||||||
{render(node.children, depth + 1)}
|
{render(node.children, depth + 1)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1210,14 +1219,9 @@ function SidebarPanel({
|
|||||||
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">
|
<Text size="sm" tone="muted">
|
||||||
勾选要在侧边栏显示的分组;子分组可以单独勾选,父分组不勾选也不影响子项。
|
勾选要在侧边栏显示的分组;子分组可以单独勾选,父分组不勾选也不影响子项。开关修改后立即保存。
|
||||||
</Text>
|
</Text>
|
||||||
{render(groups, 0)}
|
{render(groups, 0)}
|
||||||
<Space gap="sm" justify="end">
|
|
||||||
<Button loading={saving} icon={<Icon name="check_line" />} onClick={onSave}>
|
|
||||||
保存侧边栏
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user