Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
S
sumeiqiao
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
xuchentao
sumeiqiao
Commits
7d534b67
Commit
7d534b67
authored
Jul 23, 2026
by
xuchentao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: keep article categories in sync
parent
27f939e3
Pipeline
#413
passed with stages
in 17 seconds
Changes
9
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
36 additions
and
28 deletions
+36
-28
app.js
public/admin/app.js
+5
-4
Header.astro
src/components/Header.astro
+10
-2
site.ts
src/data/site.ts
+0
-4
articles.ts
src/lib/articles.ts
+7
-3
cms-api.ts
src/lib/cms-api.ts
+1
-2
index.astro
src/pages/articles/index.astro
+2
-2
[page].astro
src/pages/articles/page/[page].astro
+2
-2
index.astro
src/pages/articles/topic/[slug]/index.astro
+5
-6
[page].astro
src/pages/articles/topic/[slug]/page/[page].astro
+4
-3
No files found.
public/admin/app.js
View file @
7d534b67
...
...
@@ -223,10 +223,10 @@ async function createCategory() {
const
name
=
input
.
value
.
trim
();
$
(
"#category-error"
).
textContent
=
""
;
try
{
const
result
=
await
api
(
"/categories"
,
{
method
:
"POST
"
,
body
:
{
name
}
});
categories
=
result
.
categories
;
await
triggerBuild
(
"/categories"
,
{
action
:
"添加分类
"
,
body
:
{
name
}
});
await
loadCategories
()
;
input
.
value
=
""
;
fillCategories
(
result
.
category
);
fillCategories
(
name
);
dirty
=
true
;
closeCategoryMenu
();
if
(
!
$
(
"#category-modal"
).
classList
.
contains
(
"hidden"
))
await
loadCategoryManager
();
...
...
@@ -383,9 +383,10 @@ $("#manage-categories").addEventListener("click", openCategoryManager);
$
(
"#close-category-modal"
).
addEventListener
(
"click"
,
()
=>
hide
(
"#category-modal"
));
$
(
"#manager-add-category"
).
addEventListener
(
"click"
,
async
()
=>
{
const
input
=
$
(
"#manager-new-category"
);
const
name
=
input
.
value
.
trim
();
$
(
"#manager-category-error"
).
textContent
=
""
;
try
{
await
api
(
"/categories"
,
{
method
:
"POST"
,
body
:
{
name
:
input
.
value
.
trim
()
}
});
await
triggerBuild
(
"/categories"
,
{
action
:
"添加分类"
,
body
:
{
name
}
});
input
.
value
=
""
;
await
loadCategoryManager
();
}
catch
(
error
)
{
$
(
"#manager-category-error"
).
textContent
=
error
.
message
;
}
...
...
src/components/Header.astro
View file @
7d534b67
---
import logo from "../../横向-绿0013.svg";
import { site } from "../data/site";
import { categoryToSlug, getCategoryNames } from "../lib/articles";
const path = Astro.url.pathname;
const isActive = (href: string) => href === "/" ? path === "/" : path.startsWith(href);
const categoryChildren = (await getCategoryNames()).map((label) => ({
href: `/articles/topic/${categoryToSlug(label)}/`,
label,
}));
const nav = site.nav.map((item) => item.href === "/articles/"
? { ...item, children: [{ href: "/articles/", label: "全部资讯" }, ...categoryChildren] }
: item);
---
<div class="top-note">
<div class="container top-note__inner">
...
...
@@ -20,7 +28,7 @@ const isActive = (href: string) => href === "/" ? path === "/" : path.startsWith
<nav class="desktop-nav" aria-label="主导航">
<ul>
{
site.
nav.map((item) => (
{nav.map((item) => (
<li class="nav-item">
<a class="nav-link" href={item.href} aria-current={isActive(item.href) ? "page" : undefined} aria-haspopup="true">
{item.label}<span class="nav-chevron" aria-hidden="true">⌄</span>
...
...
@@ -44,7 +52,7 @@ const isActive = (href: string) => href === "/" ? path === "/" : path.startsWith
<span></span><span></span><span></span>
</summary>
<nav data-mobile-nav aria-label="移动端导航">
{
site.
nav.map((item) => (
{nav.map((item) => (
<div class="mobile-nav__group">
<a class="mobile-nav__primary" href={item.href} aria-current={isActive(item.href) ? "page" : undefined}>{item.label}</a>
<div class="mobile-subnav">
...
...
src/data/site.ts
View file @
7d534b67
...
...
@@ -169,10 +169,6 @@ export const site = {
label
:
"健康资讯"
,
children
:
[
{
href
:
"/articles/"
,
label
:
"全部资讯"
},
{
href
:
"/articles/topic/body-age/"
,
label
:
"体龄管理"
},
{
href
:
"/articles/topic/meridian/"
,
label
:
"经络调理"
},
{
href
:
"/articles/topic/wellness/"
,
label
:
"健康科普"
},
{
href
:
"/articles/topic/store-growth/"
,
label
:
"门店经营"
},
],
},
{
...
...
src/lib/articles.ts
View file @
7d534b67
import
{
getPublishedArticles
as
readPublishedArticles
,
type
Article
}
from
"./article-store"
;
import
{
get
CategoryNames
as
readCategoryNames
,
get
PublishedArticles
as
readPublishedArticles
,
type
Article
}
from
"./article-store"
;
export
type
{
Article
};
export
const
PAGE_SIZE
=
9
;
...
...
@@ -28,14 +28,18 @@ export async function getPublishedArticles(): Promise<Article[]> {
return
readPublishedArticles
();
}
export
async
function
getCategoryNames
():
Promise
<
string
[]
>
{
return
readCategoryNames
();
}
export
interface
CategoryInfo
{
name
:
string
;
slug
:
string
;
count
:
number
;
}
export
function
getCategories
(
items
:
Article
[]):
CategoryInfo
[]
{
const
counts
=
new
Map
<
string
,
number
>
();
export
function
getCategories
(
items
:
Article
[]
,
categoryNames
:
string
[]
=
[]
):
CategoryInfo
[]
{
const
counts
=
new
Map
<
string
,
number
>
(
categoryNames
.
map
((
name
)
=>
[
name
,
0
])
);
for
(
const
item
of
items
)
counts
.
set
(
item
.
data
.
category
,
(
counts
.
get
(
item
.
data
.
category
)
??
0
)
+
1
);
return
[...
counts
.
entries
()]
.
map
(([
name
,
count
])
=>
({
name
,
slug
:
categoryToSlug
(
name
),
count
}))
...
...
src/lib/cms-api.ts
View file @
7d534b67
...
...
@@ -162,8 +162,7 @@ export async function handleCmsApi(request: Request, routeValue: string, clientA
if
(
method
===
"GET"
)
return
json
({
categories
:
await
getCategoryNames
(),
stats
:
await
getCategoryStats
()
});
const
body
=
await
bodyOf
(
request
);
if
(
method
===
"POST"
)
{
const
categories
=
await
withSiteBuildLock
(()
=>
addCategory
(
body
.
name
));
return
json
({
ok
:
true
,
category
:
String
(
body
.
name
||
""
).
trim
(),
categories
,
stats
:
await
getCategoryStats
()
},
201
);
return
startOrConflict
(
async
()
=>
{
await
addCategory
(
body
.
name
);
});
}
if
(
method
===
"PATCH"
)
{
return
startOrConflict
(
async
()
=>
{
await
renameCategory
(
body
.
current
,
body
.
name
);
});
...
...
src/pages/articles/index.astro
View file @
7d534b67
---
import ArticlesView from "../../components/ArticlesView.astro";
import { getPublishedArticles, getCategories, pageSlice, lastPageOf } from "../../lib/articles";
import { getPublishedArticles, getCategor
yNames, getCategor
ies, pageSlice, lastPageOf } from "../../lib/articles";
const all = await getPublishedArticles();
const categories = getCategories(all);
const categories = getCategories(all
, await getCategoryNames()
);
const items = pageSlice(all, 1);
---
<ArticlesView items={items} categories={categories} totalCount={all.length} activeSlug={null} activeName={null} currentPage={1} lastPage={lastPageOf(all.length)} basePath="/articles" />
src/pages/articles/page/[page].astro
View file @
7d534b67
---
import
ArticlesView
from
"../../../components/ArticlesView.astro"
;
import
{
getPublishedArticles
,
getCategories
,
pageSlice
,
lastPageOf
}
from
"../../../lib/articles"
;
import
{
getPublishedArticles
,
getCategor
yNames
,
getCategor
ies
,
pageSlice
,
lastPageOf
}
from
"../../../lib/articles"
;
export
async
function
getStaticPaths
()
{
const
articles
=
await
getPublishedArticles
();
...
...
@@ -12,4 +12,4 @@ export async function getStaticPaths() {
const
all
=
await
getPublishedArticles
();
const
page
=
Number
(
Astro
.
params
.
page
);
---
<
ArticlesView
items
=
{
pageSlice
(
all
,
page
)}
categories
=
{
getCategories
(
all
)}
totalCount
=
{
all
.
length
}
activeSlug
=
{
null
}
activeName
=
{
null
}
currentPage
=
{
page
}
lastPage
=
{
lastPageOf
(
all
.
length
)}
basePath
=
"/articles"
/>
<
ArticlesView
items
=
{
pageSlice
(
all
,
page
)}
categories
=
{
getCategories
(
all
,
await
getCategoryNames
()
)}
totalCount
=
{
all
.
length
}
activeSlug
=
{
null
}
activeName
=
{
null
}
currentPage
=
{
page
}
lastPage
=
{
lastPageOf
(
all
.
length
)}
basePath
=
"/articles"
/>
src/pages/articles/topic/[slug]/index.astro
View file @
7d534b67
---
import
ArticlesView
from
"../../../../components/ArticlesView.astro"
;
import
{
getPublishedArticl
es
,
getCategories
,
slugToCategory
,
pageSlice
,
lastPageOf
}
from
"../../../../lib/articles"
;
import
{
categoryToSlug
,
getPublishedArticles
,
getCategoryNam
es
,
getCategories
,
slugToCategory
,
pageSlice
,
lastPageOf
}
from
"../../../../lib/articles"
;
export
async
function
getStaticPaths
()
{
const
articles
=
await
getPublishedArticles
();
return
getCategories
(
articles
)
.
map
((
category
)
=>
({
params
:
{
slug
:
category
.
slug
},
props
:
{
categoryName
:
category
.
name
},
return
(
await
getCategoryNames
())
.
map
((
categoryName
)
=>
({
params
:
{
slug
:
categoryToSlug
(
categoryName
)
},
props
:
{
categoryName
},
}));
}
const
all
=
await
getPublishedArticles
();
const
categories
=
getCategories
(
all
);
const
categories
=
getCategories
(
all
,
await
getCategoryNames
()
);
const
categoryName
=
String
(
Astro
.
props
.
categoryName
||
slugToCategory
(
Astro
.
params
.
slug
!
,
categories
.
map
((
item
)
=>
item
.
name
))
||
""
);
const
filtered
=
all
.
filter
((
item
)
=>
item
.
data
.
category
===
categoryName
);
---
...
...
src/pages/articles/topic/[slug]/page/[page].astro
View file @
7d534b67
---
import
ArticlesView
from
"../../../../../components/ArticlesView.astro"
;
import
{
getPublishedArticles
,
getCategories
,
slugToCategory
,
pageSlice
,
lastPageOf
}
from
"../../../../../lib/articles"
;
import
{
getPublishedArticles
,
getCategor
yNames
,
getCategor
ies
,
slugToCategory
,
pageSlice
,
lastPageOf
}
from
"../../../../../lib/articles"
;
export
async
function
getStaticPaths
()
{
const
articles
=
await
getPublishedArticles
();
const
categories
=
getCategories
(
articles
,
await
getCategoryNames
());
const
paths
:
Array
<
{
params
:
{
slug
:
string
;
page
:
string
};
props
:
{
categoryName
:
string
}
}
>
=
[];
for
(
const
category
of
getCategories
(
articles
)
)
{
for
(
const
category
of
categories
)
{
for
(
let
page
=
2
;
page
<=
lastPageOf
(
category
.
count
);
page
+=
1
)
{
paths
.
push
({
params
:
{
slug
:
category
.
slug
,
page
:
String
(
page
)
},
props
:
{
categoryName
:
category
.
name
}
});
}
...
...
@@ -14,7 +15,7 @@ export async function getStaticPaths() {
}
const
all
=
await
getPublishedArticles
();
const
categories
=
getCategories
(
all
);
const
categories
=
getCategories
(
all
,
await
getCategoryNames
()
);
const
categoryName
=
String
(
Astro
.
props
.
categoryName
||
slugToCategory
(
Astro
.
params
.
slug
!
,
categories
.
map
((
item
)
=>
item
.
name
))
||
""
);
const
filtered
=
all
.
filter
((
item
)
=>
item
.
data
.
category
===
categoryName
);
const
page
=
Number
(
Astro
.
params
.
page
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment