feat(health): 内容管理模块 — 审核/分类/标签/富文本编辑器
后端: - 文章审核状态机:draft → pending_review → published(含 reject/unpublish) - 文章分类 CRUD(article_category entity + service + handler) - 文章标签 CRUD(article_tag + article_article_tag 关联) - 文章修订版快照(article_revision) - 阅读计数、排序、slug、审核备注 - 新增 health.articles.review 权限 前端: - ArticleManageList:状态标签页 + 分类筛选 + 关键字搜索 + 审核操作 - ArticleEditor:Wangeditor 富文本编辑器 + 元数据侧栏 - ArticleCategoryManage:分类 CRUD + 父子层级 - ArticleTagManage:标签 CRUD 修复: - diagnosis_service/health_data_service/dialysis_service: 补充 key_version 字段 - ArticleCategoryManage: 补充 Select 组件导入
This commit is contained in:
@@ -1,52 +1,122 @@
|
||||
import client from '../client';
|
||||
import type { PaginatedResponse } from '../types';
|
||||
|
||||
// --- Types ---
|
||||
// --- Article Types ---
|
||||
|
||||
export type ArticleStatus = 'draft' | 'pending_review' | 'published' | 'rejected';
|
||||
export type ArticleContentType = 'rich_text' | 'markdown';
|
||||
|
||||
export interface ArticleListItem {
|
||||
id: string;
|
||||
title: string;
|
||||
summary?: string;
|
||||
cover_image?: string;
|
||||
category?: string;
|
||||
content_type: ArticleContentType;
|
||||
status: ArticleStatus;
|
||||
slug?: string;
|
||||
category_id?: string;
|
||||
category_name?: string;
|
||||
tags?: ArticleTagItem[];
|
||||
author?: string;
|
||||
reviewed_by?: string;
|
||||
reviewed_at?: string;
|
||||
review_note?: string;
|
||||
view_count: number;
|
||||
sort_order: number;
|
||||
published_at?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface Article extends ArticleListItem {
|
||||
content?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface CreateArticleReq {
|
||||
title: string;
|
||||
summary?: string;
|
||||
content?: string;
|
||||
content_type?: ArticleContentType;
|
||||
cover_image?: string;
|
||||
category?: string;
|
||||
author?: string;
|
||||
published_at?: string;
|
||||
slug?: string;
|
||||
category_id?: string;
|
||||
tag_ids?: string[];
|
||||
sort_order?: number;
|
||||
}
|
||||
|
||||
export interface UpdateArticleReq {
|
||||
title?: string;
|
||||
summary?: string;
|
||||
content?: string;
|
||||
content_type?: ArticleContentType;
|
||||
cover_image?: string;
|
||||
category?: string;
|
||||
author?: string;
|
||||
published_at?: string;
|
||||
slug?: string;
|
||||
category_id?: string;
|
||||
tag_ids?: string[];
|
||||
sort_order?: number;
|
||||
version: number;
|
||||
}
|
||||
|
||||
// --- API ---
|
||||
export interface ArticleListParams {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
status?: ArticleStatus;
|
||||
category_id?: string;
|
||||
tag_id?: string;
|
||||
keyword?: string;
|
||||
}
|
||||
|
||||
// --- Category Types ---
|
||||
|
||||
export interface ArticleCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
slug?: string;
|
||||
parent_id?: string;
|
||||
parent_name?: string;
|
||||
sort_order: number;
|
||||
description?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CreateCategoryReq {
|
||||
name: string;
|
||||
slug?: string;
|
||||
parent_id?: string;
|
||||
sort_order?: number;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface UpdateCategoryReq {
|
||||
name?: string;
|
||||
slug?: string;
|
||||
parent_id?: string;
|
||||
sort_order?: number;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// --- Tag Types ---
|
||||
|
||||
export interface ArticleTagItem {
|
||||
id: string;
|
||||
name: string;
|
||||
slug?: string;
|
||||
color?: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreateTagReq {
|
||||
name: string;
|
||||
slug?: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
// --- Article API ---
|
||||
|
||||
export const articleApi = {
|
||||
list: async (params: {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
category?: string;
|
||||
}) => {
|
||||
list: async (params: ArticleListParams) => {
|
||||
const { data } = await client.get<{
|
||||
success: boolean;
|
||||
data: PaginatedResponse<ArticleListItem>;
|
||||
@@ -85,4 +155,108 @@ export const articleApi = {
|
||||
}>(`/health/articles/${id}`);
|
||||
return data.data;
|
||||
},
|
||||
|
||||
submit: async (id: string, version: number) => {
|
||||
const { data } = await client.post<{
|
||||
success: boolean;
|
||||
data: Article;
|
||||
}>(`/health/articles/${id}/submit`, { version });
|
||||
return data.data;
|
||||
},
|
||||
|
||||
approve: async (id: string, version: number) => {
|
||||
const { data } = await client.post<{
|
||||
success: boolean;
|
||||
data: Article;
|
||||
}>(`/health/articles/${id}/approve`, { version });
|
||||
return data.data;
|
||||
},
|
||||
|
||||
reject: async (id: string, version: number, review_note: string) => {
|
||||
const { data } = await client.post<{
|
||||
success: boolean;
|
||||
data: Article;
|
||||
}>(`/health/articles/${id}/reject`, { version, review_note });
|
||||
return data.data;
|
||||
},
|
||||
|
||||
unpublish: async (id: string, version: number) => {
|
||||
const { data } = await client.post<{
|
||||
success: boolean;
|
||||
data: Article;
|
||||
}>(`/health/articles/${id}/unpublish`, { version });
|
||||
return data.data;
|
||||
},
|
||||
|
||||
view: async (id: string) => {
|
||||
const { data } = await client.post<{
|
||||
success: boolean;
|
||||
data: Article;
|
||||
}>(`/health/articles/${id}/view`);
|
||||
return data.data;
|
||||
},
|
||||
};
|
||||
|
||||
// --- Category API ---
|
||||
|
||||
export const articleCategoryApi = {
|
||||
list: async () => {
|
||||
const { data } = await client.get<{
|
||||
success: boolean;
|
||||
data: ArticleCategory[];
|
||||
}>('/health/article-categories');
|
||||
return data.data;
|
||||
},
|
||||
|
||||
create: async (req: CreateCategoryReq) => {
|
||||
const { data } = await client.post<{
|
||||
success: boolean;
|
||||
data: ArticleCategory;
|
||||
}>('/health/article-categories', req);
|
||||
return data.data;
|
||||
},
|
||||
|
||||
update: async (id: string, req: UpdateCategoryReq) => {
|
||||
const { data } = await client.put<{
|
||||
success: boolean;
|
||||
data: ArticleCategory;
|
||||
}>(`/health/article-categories/${id}`, req);
|
||||
return data.data;
|
||||
},
|
||||
|
||||
delete: async (id: string) => {
|
||||
const { data } = await client.delete<{
|
||||
success: boolean;
|
||||
data: null;
|
||||
}>(`/health/article-categories/${id}`);
|
||||
return data.data;
|
||||
},
|
||||
};
|
||||
|
||||
// --- Tag API ---
|
||||
|
||||
export const articleTagApi = {
|
||||
list: async () => {
|
||||
const { data } = await client.get<{
|
||||
success: boolean;
|
||||
data: ArticleTagItem[];
|
||||
}>('/health/article-tags');
|
||||
return data.data;
|
||||
},
|
||||
|
||||
create: async (req: CreateTagReq) => {
|
||||
const { data } = await client.post<{
|
||||
success: boolean;
|
||||
data: ArticleTagItem;
|
||||
}>('/health/article-tags', req);
|
||||
return data.data;
|
||||
},
|
||||
|
||||
delete: async (id: string) => {
|
||||
const { data } = await client.delete<{
|
||||
success: boolean;
|
||||
data: null;
|
||||
}>(`/health/article-tags/${id}`);
|
||||
return data.data;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user