diff --git a/HANDOFF.md b/HANDOFF.md index 38d1799..cd376ae 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -100,7 +100,7 @@ README.md 运行、路由、库结构、登录说明 | 分组管理 | 树形表格:新建根分组(左侧)、加子分组、编辑(名称/上级/格式/图标/排序/状态)、删除;行内状态开关即时生效;图标可留空 | | 内容 | 选分组以列表展示条目(选主分组时包含所有子分组);列:标题 / 跳转链接 / 所属分组 / 操作;弹窗新增/编辑,编辑时显示不可改的「标识」 | | 侧边栏 | 分组树逐项勾选,开关在右侧、**拨动即保存**(无单独保存/撤销按钮);子分组可单独勾选 | -| 首页卡片 | 新建/改名/删除卡片;勾选若干分组(可混合格式),每组单独设条数 | +| 首页卡片 | 新建/改名/删除卡片;分组开关在右侧、勾选即保存,标题与条数失焦保存(无每卡保存按钮),可混合格式 | | 页头 | 启用开关、速度、条目、标签与色调 | | 资料 | 头像 URL、名称、角色、简介、位置 | | 系统 | 修改密码、下载备份 | diff --git a/docs/ADMIN-PLAN.md b/docs/ADMIN-PLAN.md index 7da2074..23c2790 100644 --- a/docs/ADMIN-PLAN.md +++ b/docs/ADMIN-PLAN.md @@ -147,6 +147,12 @@ DELETE /api/items/:id ## 11. 实施记录 +### P13(2026-10-31,首页卡片按侧边栏逻辑) + +- 每张卡片的分组勾选改为与侧边栏一致:标题在左、开关在右、勾选/取消即时保存(`PUT /api/cards/{id}/groups`),去掉每张卡的「保存」按钮; +- 卡片标题、单组预览条数改为失焦保存;条数清空表示使用默认; +- 卡片删除仍保留图标按钮;验证:无卡片级保存按钮、开关在右、勾选后 `card_group` 即时更新、条数 2 与改名均落库。 + ### P12(2026-10-31,内容/侧边栏交互微调) - 内容页与条目弹窗的分组下拉只显示分组名,去掉格式后缀与缩进空格,选项统一左对齐; diff --git a/stash-web/src/App.css b/stash-web/src/App.css index 9b6a1fb..e50d727 100644 --- a/stash-web/src/App.css +++ b/stash-web/src/App.css @@ -1281,6 +1281,21 @@ a.entry-href:hover { min-width: 0; } +/* 首页卡片:标题行 + 分组勾选行(开关在右) */ +.card-title-row { + display: flex; + align-items: center; + gap: var(--su-space-2); +} + +.card-group-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 100px auto; + align-items: center; + gap: var(--su-space-2); + padding: 2px 0; +} + .admin-category-row > * { min-width: 0; } diff --git a/stash-web/src/components/admin/AdminNext.tsx b/stash-web/src/components/admin/AdminNext.tsx index 66db7e0..e2cf079 100644 --- a/stash-web/src/components/admin/AdminNext.tsx +++ b/stash-web/src/components/admin/AdminNext.tsx @@ -1259,7 +1259,7 @@ function CardsPanel({ return ( - 卡片只是展示组合:新建卡片后勾选要展示的分组(可混合格式),每张卡可单独设条数。 + 卡片只是展示组合:新建卡片后勾选要展示的分组(可混合格式),开关和条数修改后即时保存。
@@ -1320,24 +1320,9 @@ function CardRow({ ) const [busy, setBusy] = useState(false) - const toggle = (key: string, checked: boolean) => { - setSelected((current) => { - const next = { ...current } - if (checked) next[key] = '' - else delete next[key] - return next - }) - } - - const save = async () => { + const persistGroups = async (nextSelected: Record) => { setBusy(true) - const titleResult = await updateCard(card.id, { title }) - if (!titleResult.ok) { - setBusy(false) - toast.error(titleResult.error) - return - } - const groups = Object.entries(selected).map(([groupKey, limit]) => ({ + const groups = Object.entries(nextSelected).map(([groupKey, limit]) => ({ groupKey, previewLimit: limit.trim() === '' ? undefined : Number(limit), })) @@ -1345,25 +1330,46 @@ function CardRow({ setBusy(false) if (!result.ok) { toast.error(result.error) + onReload() + return + } + onReload() + } + + const toggle = (key: string, checked: boolean) => { + const next = { ...selected } + if (checked) next[key] = '' + else delete next[key] + setSelected(next) + void persistGroups(next) + } + + const saveTitle = async () => { + const next = title.trim() + if (next === '' || next === card.title) { + setTitle(card.title) + return + } + const result = await updateCard(card.id, { title: next }) + if (!result.ok) { + toast.error(result.error) + setTitle(card.title) return } - toast.success('卡片已保存') onReload() } return (
-
+
setTitle(event.target.value)} + onBlur={() => void saveTitle()} />
-