diff --git a/src/db/entries.rs b/src/db/entries.rs
index d56cde7..6709633 100644
--- a/src/db/entries.rs
+++ b/src/db/entries.rs
@@ -31,6 +31,7 @@ pub struct EntryRecord {
}
#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
pub struct EntryPatch {
pub name: String,
#[serde(default)]
diff --git a/stash-web/src/App.css b/stash-web/src/App.css
index b0fc63e..dcb1cb7 100644
--- a/stash-web/src/App.css
+++ b/stash-web/src/App.css
@@ -1029,7 +1029,7 @@
.admin-entry-row {
display: grid;
- grid-template-columns: minmax(0, 1fr) 140px auto;
+ grid-template-columns: minmax(0, 1fr) 140px auto auto;
align-items: center;
gap: var(--su-space-2);
padding: var(--su-space-2) 0;
diff --git a/stash-web/src/components/admin/AdminPage.tsx b/stash-web/src/components/admin/AdminPage.tsx
index df317f6..ca56bdd 100644
--- a/stash-web/src/components/admin/AdminPage.tsx
+++ b/stash-web/src/components/admin/AdminPage.tsx
@@ -37,7 +37,7 @@ import {
type SettingsTone,
type SiteSettings,
} from '../../settings'
-import { createItem, deleteItem } from '../../items'
+import { createItem, deleteItem, fetchItem, saveItem } from '../../items'
/**
* 站点设置页(/admin):页头滚动信息 + 侧栏顺序/显隐。
@@ -538,6 +538,12 @@ function SidebarForm({
/>
))}
+
+ {category.kind === 'apps' && (
+
+
+
+ )}
)
})}
@@ -1051,7 +1057,13 @@ function NewGroupRow({ sectionId, onReload }: { sectionId: string; onReload: ()
)
}
-// ---------- 内容(新增条目) ----------
+// ---------- 内容(条目列表 + 新增/编辑弹窗) ----------
+
+type EntryRow = EntrySummary & {
+ groupName?: string
+ sectionId: string
+ groupId?: string
+}
function ContentManager({
categories,
@@ -1061,23 +1073,16 @@ function ContentManager({
onReload: () => void
}) {
const [sectionId, setSectionId] = useState(categories[0]?.id ?? '')
- const [groupId, setGroupId] = useState('')
- const [key, setKey] = useState('')
- const [name, setName] = useState('')
- const [href, setHref] = useState('')
- const [description, setDescription] = useState('')
- const [icon, setIcon] = useState('')
- const [cover, setCover] = useState('')
- const [tags, setTags] = useState('')
- const [comment, setComment] = useState('')
- const [body, setBody] = useState('')
- const [busy, setBusy] = useState(false)
- const [pendingEntry, setPendingEntry] = useState(null)
+ const [dialog, setDialog] = useState<{
+ open: boolean
+ key: string | null
+ location: { sectionId: string; groupId?: string } | null
+ }>({ open: false, key: null, location: null })
+ const [pendingEntry, setPendingEntry] = useState(null)
const [deleting, setDeleting] = useState(false)
const section =
categories.find((category) => category.id === sectionId) ?? categories[0]
- const groups = section?.kind === 'apps' ? (section.groups ?? []) : []
useEffect(() => {
if (categories.length === 0) return
@@ -1086,17 +1091,7 @@ function ContentManager({
}
}, [categories, sectionId])
- useEffect(() => {
- if (section?.kind !== 'apps') return
- if (!groups.some((group) => group.id === groupId)) {
- setGroupId(groups[0]?.id ?? '')
- }
- }, [section, groups, groupId])
-
- const nodeKey =
- section === undefined ? '' : section.kind === 'apps' ? groupId : section.id
-
- const entries =
+ const entries: EntryRow[] =
section === undefined
? []
: section.kind === 'apps'
@@ -1104,56 +1099,15 @@ function ContentManager({
(group.items ?? []).map((item) => ({
...item,
groupName: group.name,
+ sectionId: section.id,
+ groupId: group.id,
})),
)
- : (section.items ?? []).map((item) => ({ ...item, groupName: undefined }))
-
- const submit = async () => {
- if (section === undefined || nodeKey === '') {
- toast.error('请先选择分区与分组')
- return
- }
-
- setBusy(true)
- const result = await createItem({
- key: key.trim(),
- nodeKey,
- item: {
- name: name.trim(),
- description,
- icon,
- tags: tags
- .split(/[,,]/)
- .map((tag) => tag.trim())
- .filter(Boolean),
- badge: '',
- comment,
- summary: '',
- body,
- cover,
- banner: '',
- href,
- },
- })
- setBusy(false)
-
- if (!result.ok) {
- toast.error(result.error)
- return
- }
-
- toast.success(`已添加「${name.trim()}」`)
- setKey('')
- setName('')
- setHref('')
- setDescription('')
- setIcon('')
- setCover('')
- setTags('')
- setComment('')
- setBody('')
- onReload()
- }
+ : (section.items ?? []).map((item) => ({
+ ...item,
+ groupName: undefined,
+ sectionId: section.id,
+ }))
const removeEntry = async () => {
if (pendingEntry === null) return
@@ -1172,8 +1126,8 @@ function ContentManager({
return (
- 在下面新增网址 / 海报 / 图文条目,并选择它挂在哪个分区、哪个分组(应用类必须选分组)。
- 新增后,分组出现在侧栏与首页卡片的条件是它被设为显示。
+ 新增网址 / 海报 / 图文条目时,选择它挂在哪个分区、哪个分组(应用类必须选分组);
+ 列表里的条目可以点铅笔修改,包括换分区 / 分组。
@@ -1187,95 +1141,15 @@ function ContentManager({
onChange={setSectionId}
/>
-
- {section?.kind === 'apps' && (
-
-
- )}
-
- {section?.kind === 'apps' && groups.length === 0 && (
-
- 这个分区还没有分组,请先到「分区」Tab 新建分组。
-
- )}
-
-
-
-
-
}
- disabled={
- section === undefined ||
- nodeKey === '' ||
- key.trim() === '' ||
- name.trim() === ''
+ onClick={() =>
+ setDialog({
+ open: true,
+ key: null,
+ location: section === undefined ? null : { sectionId: section.id },
+ })
}
- onClick={submit}
>
新增条目
@@ -1291,6 +1165,19 @@ function ContentManager({
{entry.groupName ?? '直属分区'}
+ }
+ aria-label="编辑条目"
+ onClick={() =>
+ setDialog({
+ open: true,
+ key: entry.id,
+ location: { sectionId: entry.sectionId, groupId: entry.groupId },
+ })
+ }
+ />
+
+ }
+ >
+ {loading ? (
+ 正在读取…
+ ) : (
+
+ {error !== '' && (
+
+ {error}
+
+ )}
+
+
+
+
+ {section?.kind === 'apps' && (
+
+
+ )}
+
+
+
+
+ )}
+
+ )
+}
+
+
// ---------- 通用删除确认(页面级) ----------
function ConfirmModal({
diff --git a/stash-web/src/items.ts b/stash-web/src/items.ts
index b0870f5..21ea88c 100644
--- a/stash-web/src/items.ts
+++ b/stash-web/src/items.ts
@@ -31,6 +31,8 @@ export type EntryInput = {
cover: string
banner: string
href: string
+ /** 编辑时传目标分区/分组,可移动条目 */
+ nodeKey?: string
}
export type ItemResult =