@web-mods/articles
v1.1.4
Published
Reusable articles management module
Readme
@web-mods/articles
文章管理组件库,提供完整的文章 CRUD 管理界面(列表、编辑、详情)以及服务端 HTML 渲染能力。
安装
pnpm add @web-mods/articles导航设置
文章保存时提交 navEnabled、navname、navPlacements,编辑时回显接口配置。启用导航需填写名称并选择至少一个展示位置,网站地址必填。
父导航从 GET /navigation/groups?host=...&position=... 读取,通过 POST /navigation/groups 创建并选中真实 ID,不提供前端示例选项。切换网站地址会清空父导航选择并重新加载;关闭导航会保留名称和位置。页眉暂时仅支持顶级导航,已有父导航只读保留;页尾可选父导航。
自定义 ArticleService 可实现 getNavigationGroups、createNavigationGroup;未实现查询方法时禁用导航编辑,文章保存省略导航字段,兼容旧宿主。
写操作需要 Bearer Token。默认 service 每次请求会读取当前页面的 localStorage.accessToken,并加入 Authorization: Bearer ...。自定义 service 可复用宿主凭证:
const articleService = createArticleService('https://api-blog.aisee.live', {
headers: () => ({ Authorization: `Bearer ${getAccessToken()}` }),
})本地调试运行 pnpm dev(或 pnpm playground),访问 http://localhost:5173/articles。页面使用默认 service,直接请求 src/service.ts 中配置的 https://api-blog.aisee.live。仅监听组件库构建请运行 pnpm build:watch。
接口约定见 技术交接文档。
Peer dependencies:
react >=18、react-dom >=18
react-router仅在 react-router 环境下需要(见下方适配说明)。dayjs、lucide-react、@monaco-editor/react均已打包在内,无需单独安装。
快速开始
1. 配置 ArticleService(可选)
service prop 不传即使用内置默认值,无需任何配置。
只有以下情况才需要手动配置:
自定义 API 地址:
import { createArticleService } from '@web-mods/articles'
const articleService = createArticleService('https://your-api.example.com')
// 然后传给 Provider:<ArticlesProvider service={articleService} ...>完全自定义请求逻辑(如附带鉴权 Header、适配不同数据格式等):
import type { ArticleService } from '@web-mods/articles'
const articleService: ArticleService = {
getList: (params) => api.get('/news', { params }), // 返回 { items, total }
getById: (id) => api.get(`/news/${id}`),
create: (data) => api.post('/news', data),
update: (id, data) => api.put(`/news/${id}`, data),
delete: (id) => api.delete(`/news/${id}`),
getByPathname: (pathname) => api.get(`/news/detail?pathname=${pathname}`), // 可选,服务端渲染时使用
}getList 返回格式:
{ items: Article[], total?: number }2. 配置路由
以 React Router v7 为例,用 layout 包裹所有文章相关路由:
// routes.ts
import { layout, route, prefix } from '@react-router/dev/routes'
export default [
layout('./pages/articles/layout.tsx', [
route('articles', './pages/articles/index.tsx'),
route('articles/create', './pages/articles/edit.tsx'),
route('articles/:id/edit', './pages/articles/edit.tsx'),
route('articles/:id/detail', './pages/articles/detail.tsx'),
]),
]3. 创建 Layout(注入 Provider)
组件库支持两种框架环境,写法略有不同:
React Router v7
用 ReactRouterAdapter 桥接路由,这是唯一需要 react-router 的地方:
适配器应放在 ArticlesProvider 内,可包裹 <Outlet /> 或整个 <Routes>。编辑和详情页的文章 ID 会从当前 URL 匹配,因此刷新或直接访问也能正常加载,不依赖跳转时的 state。
// pages/articles/layout.tsx
import { Outlet } from 'react-router'
import { ArticlesProvider, ReactRouterAdapter } from '@web-mods/articles'
import '@web-mods/articles/style.css'
export default function ArticlesLayout() {
return (
<ArticlesProvider basePath="/articles">
<ReactRouterAdapter>
<Outlet />
</ReactRouterAdapter>
</ArticlesProvider>
)
}Next.js(或其他非 react-router 框架)
通过 Provider 的 navigate prop 注入路由能力,页面组件通过 id prop 接收参数:
// app/articles/layout.tsx
'use client'
import { useRouter } from 'next/navigation'
import { ArticlesProvider } from '@web-mods/articles'
import '@web-mods/articles/style.css'
export default function ArticlesLayout({ children }: { children: React.ReactNode }) {
const router = useRouter()
return (
<ArticlesProvider basePath="/articles" navigate={(path) => router.push(path)}>
{children}
</ArticlesProvider>
)
}// app/articles/[id]/edit/page.tsx
'use client'
import { useParams } from 'next/navigation'
import { ArticleEdit } from '@web-mods/articles'
export default function EditPage() {
const { id } = useParams<{ id: string }>()
return <ArticleEdit id={id} />
}4. 使用页面组件
// React Router:无需传 props,路由参数由 ReactRouterAdapter 自动注入
import { ArticleList, ArticleEdit, ArticleDetail } from '@web-mods/articles'
export default function ListPage() { return <ArticleList /> }
export default function EditPage() { return <ArticleEdit /> }
export default function DetailPage() { return <ArticleDetail /> }
// Next.js / 其他框架:通过 id prop 传入
export default function EditPage() { return <ArticleEdit id={id} /> }
export default function DetailPage() { return <ArticleDetail id={id} /> }ArticlesProvider 配置项
| 属性 | 类型 | 必填 | 说明 |
|------|------|------|------|
| basePath | string | ✅ | 文章路由根路径,如 /articles |
| service | ArticleService | — | 数据服务,不传使用内置默认值 |
| navigate | (path: string) => void | — | 非 react-router 环境必传,由框架路由实现跳转 |
| previewBaseUrl | string | — | 预览 iframe 注入 <base href="...",用于正确加载相对路径资源 |
| previewTailwind | boolean | — | 预览时自动注入 Tailwind CDN,适合内容使用了 Tailwind 类名的场景 |
<ArticlesProvider
basePath="/blog/articles"
previewBaseUrl="https://example.com"
previewTailwind={true}
>
...
</ArticlesProvider>Article 数据结构
interface Article {
id: string
pathname: string // 前台访问路径,如 about-us
content: string // 完整 HTML 内容
host?: string // 可跳转的网站地址,如 https://example.com
createdAt?: string
updatedAt?: string
}服务端渲染(Server-side Rendering)
适用于 Next.js Route Handler、Remix、Cloudflare Workers 等支持 Web
Request/ResponseAPI 的环境。
从独立的 server 入口引入,不包含任何 React 依赖,可安全在服务端使用:
import { renderArticleByPathname } from '@web-mods/articles/server'Next.js 示例
// app/[slug]/route.ts
import { renderArticleByPathname, defaultArticleService } from '@web-mods/articles/server'
export async function GET(_req: Request, { params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
return renderArticleByPathname(slug, defaultArticleService, {
faviconHref: '/favicon.svg', // 可选,自动注入 favicon
})
}如需自定义 API 地址:
import { renderArticleByPathname, createArticleService } from '@web-mods/articles/server'
const service = createArticleService('https://your-api.example.com')
export async function GET(_req: Request, { params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
return renderArticleByPathname(slug, service)
}访问 https://your-site.com/about-us 时,浏览器直接渲染 manage 后台编辑的 HTML 内容,无任何框架包裹。
renderArticleByPathname 参数
| 参数 | 类型 | 说明 |
|------|------|------|
| pathname | string | 文章路径 |
| service | ArticleService | 需实现 getByPathname 方法 |
| options.faviconHref | string | 自动注入 favicon,如 /favicon.svg |
| options.notFoundHtml | string | 自定义 404 页面 HTML,不传则使用内置样式 |
样式
组件库使用 Tailwind CSS 构建,样式已编译打包至 dist/style.css。在 Layout 中引入一次即可,框架会在构建时将其内联进 <head>,不会有样式闪烁(FOUC):
import '@web-mods/articles/style.css'