Files
nanocode/new-media-platform/website-new/docs/COLUMN_ARTICLE_DESIGN.md
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

16 KiB
Raw Blame History

CMS 栏目管理与内容管理 技术设计方案

一、概述

1.1 设计目标

为飞点健康 CMS 后台新增两大顶级模块:

  • 栏目管理模块:负责全站分类架构、前端展示样式配置、导航绑定。栏目本身为纯框架载体,不存储具体业务内容。
  • 内容管理模块:负责产品、专家、服务、图文资讯等各类业务内容的统一录入、编辑与维护。所有内容必须关联到栏目。

1.2 核心原则

  1. 本期只实现后台管理:访客端展示完全不开发,但后台设计预留访客端扩展接口。
  2. 栏目是框架,内容是填充:栏目本身无内容,内容由内容管理模块生成后关联到栏目。
  3. 现有新闻资讯模块完全保留:新的栏目管理和内容管理是独立顶级模块,与现有新闻资讯并行存在,本期不改造、不合并。
  4. 命名与现有项目严格一致:参照 news_category.py、home_expert.py、nav_menu.py 等现有文件命名风格,不使用 cms_ 前缀。

1.3 模块关系图

后台管理
├── 仪表盘
├── 首页管理
├── 导航菜单
├── 新闻资讯(现有,保留不动)
│   ├── 资讯管理
│   └── 栏目管理(现有,保留不动)
├── 图片管理
├── [NEW] 栏目管理(新顶级模块)
│   └── 栏目列表
├── [NEW] 内容管理(新顶级模块)
│   ├── 内容列表
│   ├── 添加内容
│   └── 采集内容
└── 系统设置

二、命名规范

2.1 命名与现有项目保持一致

层级 现有风格示例 新模块命名
后端模型文件 news_category.py / home_expert.py column.py(栏目) / article.py(内容)
后端模型类 NewsCategory / HomeExpert ColumnModel / Article
数据库表 news_categories / home_experts columns / articles
后端路由文件 news.py / home_experts.py columns.py / articles.py
前端页面目录 pages/admin/news/ pages/admin/columns/ / pages/admin/articles/
前端组件名 CategoryList.jsx / PostEdit.jsx ColumnList.jsx / ArticleList.jsx / ArticleEdit.jsx / ArticleCrawl.jsx
API 前缀 /api/news/posts / /api/home-experts /api/columns / /api/articles

2.2 关于 ColumnModel 类名说明

SQLAlchemy 内置了 Column 类(sqlalchemy.Column),因此栏目模型类名使用 ColumnModel 以避免命名冲突。这是项目中已有的做法(如 Image 模型类名不会与 Python 内置冲突)。


三、数据库设计

3.1 columns 表(栏目表)

栏目是纯框架载体,本身不存储具体业务内容。

字段 类型 约束 说明
id Integer PK, auto_increment 主键
name String(50) NOT NULL 栏目名称
slug String(50) UNIQUE, NOT NULL URL标识(拼音转换生成)
description String(200) NULL 栏目描述
parent_id Integer FK -> columns.id, NULL 父栏目ID,NULL表示顶级栏目
sort_order Integer DEFAULT 0 排序权重,数字越小越靠前
is_enabled Boolean DEFAULT True 是否前端可见(True=可见,False=隐藏)
display_mode String(20) DEFAULT 'list' 展示模式:list 列表式 / card 卡片式
layout_style String(20) DEFAULT 'left' 布局样式:left 图左文右 / right 图右文左 / top 上图下文
created_at DateTime server_default=now() 创建时间
updated_at DateTime server_default=now(), onupdate=now() 更新时间

索引:

  • slug 唯一索引
  • parent_id 索引(用于树形查询)

3.2 articles 表(内容表)

全站业务内容的统一载体,产品、专家、服务、图文资讯等均通过此表存储。

字段 类型 约束 说明
id Integer PK, auto_increment 主键
title String(200) NOT NULL 标题
slug String(200) UNIQUE, NOT NULL URL友好的标识(拼音转换生成)
content Text NOT NULL 正文
content_type String(10) DEFAULT 'markdown' 内容类型:markdown / html
summary String(500) NULL 摘要,留空时自动从正文提取纯文本
cover_image String(500) NULL 封面图路径
column_id Integer FK -> columns.id, NOT NULL 所属栏目ID
author String(50) NULL 作者
status String(20) DEFAULT 'draft' 状态:published 已发布 / draft 草稿
sort_order Integer DEFAULT 0 排序权重,数字越小越靠前
view_count Integer DEFAULT 0 浏览次数
created_at DateTime server_default=now() 创建时间
updated_at DateTime server_default=now(), onupdate=now() 更新时间

索引:

  • slug 唯一索引
  • column_id 索引(用于栏目关联查询)
  • status 索引(用于状态筛选)

四、栏目与内容的关联规则

4.1 两级栏目架构

  • 顶级栏目:parent_id = NULL
  • 二级栏目:parent_id 指向某个顶级栏目
  • 本期最多支持两级,代码层面不限制层级,但 API 校验按两级处理

4.2 内容关联约束(核心规则)

栏目类型 是否有二级栏目 内容关联规则
顶级栏目 无二级栏目 内容可直接关联到该顶级栏目
顶级栏目 有二级栏目 禁止内容直接关联,内容必须关联到其下属二级栏目
二级栏目 - 内容可直接关联

4.3 栏目删除规则

场景 处理方式
栏目无子栏目、无关联内容 直接删除
栏目无子栏目、有关联内容 + 未勾选强制删除 报错:"该栏目下还有N条内容,是否强制删除?"
栏目无子栏目、有关联内容 + 勾选强制删除 先删除所有关联内容,再删除栏目
栏目有子栏目 + 未勾选强制删除 报错:"该栏目存在N个子栏目,请先删除子栏目或勾选强制删除"
栏目有子栏目 + 勾选强制删除 递归删除所有子栏目及其关联内容,再删除当前栏目

五、后端 API 设计

5.1 栏目管理 API

路由文件:backend/app/routers/columns.py

前缀:/api/columns

方法 路径 功能 请求参数
GET / 栏目列表 tree: bool(是否返回树形结构)
GET /tree 树形栏目列表(用于下拉选择) 无
GET /{id} 栏目详情 无
POST / 创建栏目 Body: ColumnCreate
PUT /{id} 更新栏目 Body: ColumnUpdate
DELETE /{id} 删除栏目 Query: force: bool

Pydantic 模型:

class ColumnCreate(BaseModel):
    name: str = Field(..., min_length=1, max_length=50)
    slug: Optional[str] = Field(None, max_length=50)
    description: Optional[str] = Field(None, max_length=200)
    parent_id: Optional[int] = Field(None)
    sort_order: int = Field(default=0)
    is_enabled: bool = Field(default=True)
    display_mode: str = Field(default="list", pattern="^(list|card)$")
    layout_style: str = Field(default="left", pattern="^(left|right|top)$")

class ColumnUpdate(BaseModel):
    name: Optional[str] = Field(None, min_length=1, max_length=50)
    slug: Optional[str] = Field(None, min_length=1, max_length=50)
    description: Optional[str] = Field(None, max_length=200)
    parent_id: Optional[int] = Field(None)
    sort_order: Optional[int] = None
    is_enabled: Optional[bool] = None
    display_mode: Optional[str] = Field(None, pattern="^(list|card)$")
    layout_style: Optional[str] = Field(None, pattern="^(left|right|top)$")

创建栏目时的自动处理:

  • slug 为空时,自动调用 generate_slug(name) 生成拼音标识
  • parent_id 不为空时,校验父栏目是否存在,且父栏目本身不能是二级栏目(最多两级)

5.2 内容管理 API

路由文件:backend/app/routers/articles.py

前缀:/api/articles

方法 路径 功能 请求参数
GET / 内容列表(分页 + 筛选) page, size, column_id, keyword, status, start_date, end_date
GET /{id} 内容详情 无
POST / 创建内容 Body: ArticleCreate
PUT /{id} 更新内容 Body: ArticleUpdate
DELETE /{id} 删除内容 无
POST /batch-delete 批量删除 Body: {ids: number[]}
POST /batch-move 批量转移栏目 Body: {ids: number[], column_id: number}
POST /{id}/publish 发布/取消发布 Body: {is_published: bool}

Pydantic 模型:

class ArticleCreate(BaseModel):
    title: str = Field(..., min_length=1, max_length=200)
    slug: Optional[str] = Field(None, max_length=200)
    content: str = Field(..., min_length=1)
    content_type: str = Field(default="markdown", pattern="^(markdown|html)$")
    summary: Optional[str] = Field(None, max_length=500)
    cover_image: Optional[str] = Field(None, max_length=500)
    column_id: int
    author: Optional[str] = Field(None, max_length=50)
    status: str = Field(default="draft", pattern="^(published|draft)$")
    sort_order: int = Field(default=0)

class ArticleUpdate(BaseModel):
    title: Optional[str] = Field(None, min_length=1, max_length=200)
    slug: Optional[str] = Field(None, min_length=1, max_length=200)
    content: Optional[str] = Field(None, min_length=1)
    content_type: Optional[str] = Field(None, pattern="^(markdown|html)$")
    summary: Optional[str] = Field(None, max_length=500)
    cover_image: Optional[str] = Field(None, max_length=500)
    column_id: Optional[int] = None
    author: Optional[str] = Field(None, max_length=50)
    status: Optional[str] = Field(None, pattern="^(published|draft)$")
    sort_order: Optional[int] = None

创建/更新内容时的校验:

  • slug 为空时,自动调用 generate_slug(title) 生成拼音标识
  • column_id 必须指向有效栏目
  • 若关联的栏目是顶级栏目,需检查该顶级栏目下是否有二级栏目:
    • 有二级栏目 → 报错:"该栏目存在子栏目,内容必须关联到子栏目"
    • 无二级栏目 → 允许关联

摘要自动提取:

  • summary 为空时,调用 extract_summary(content) 从正文中提取纯文本摘要(去除 HTML 标签、解码 HTML 实体、清理 Markdown 标记)

六、前端页面设计

6.1 后台导航菜单

在 AdminLayout.jsx 的 menuItems 数组中新增两个顶级模块:

{
  key: 'columns',
  label: '栏目管理',
  icon: FolderTree,  // lucide-react
  children: [
    { key: 'column-list', label: '栏目列表', path: '/admin/columns' }
  ]
},
{
  key: 'articles',
  label: '内容管理',
  icon: FileStack,   // lucide-react
  children: [
    { key: 'article-list', label: '内容列表', path: '/admin/articles' },
    { key: 'article-new', label: '添加内容', path: '/admin/articles/new' },
    { key: 'article-crawl', label: '采集内容', path: '/admin/articles/crawl' }
  ]
}

6.2 页面文件结构

frontend/src/pages/admin/
├── columns/
│   └── ColumnList.jsx          # 栏目列表(树形展示)+ 增删改弹窗
└── articles/
    ├── ArticleList.jsx         # 内容列表 + 搜索筛选 + 批量操作
    ├── ArticleEdit.jsx         # 内容编辑(复用现有编辑器)
    └── ArticleCrawl.jsx        # 内容采集(复用现有采集逻辑)

6.3 栏目列表页(ColumnList.jsx)

功能:

  • 树形表格展示栏目(顶级栏目可展开查看二级栏目)
  • 每行显示:排序权重、栏目名称、slug、内容数量、显示状态
  • 操作:编辑、删除

新增/编辑弹窗表单字段:

  • 栏目名称(必填)
  • 上级栏目(下拉选择,顶级选"无")
  • URL标识(自动生成拼音)
  • 栏目描述(选填)
  • 排序权重(数字输入)
  • 是否前端可见(开关)
  • 展示模式(单选:列表式/卡片式)
  • 布局样式(单选:图左文右/图右文左/上图下文)

删除弹窗:

  • 无关联内容 → 普通确认删除
  • 有关联内容 → 警告提示 + "强制删除关联内容" 复选框

6.4 内容列表页(ArticleList.jsx)

功能:

  • 表格展示内容列表
  • 每行显示:封面缩略图、标题、所属栏目、状态、排序、更新时间
  • 顶部筛选栏:
    • 栏目筛选(树形下拉选择器)
    • 状态筛选(全部/已发布/草稿)
    • 关键词搜索(标题)
    • 发布时间范围
  • 批量操作:勾选后批量删除、批量转移栏目
  • 操作列:编辑、删除、预览

6.5 内容编辑页(ArticleEdit.jsx)

复用现有 PostEdit.jsx 的以下组件和逻辑:

  • MDEditor 编辑器(Markdown / HTML 富文本)
  • 图片上传/选择器
  • 表单布局和样式

字段调整:

  • 标题(必填)
  • URL标识(留空由后端自动生成拼音)
  • 所属栏目(树形选择器,受栏目约束规则校验)
  • 摘要(留空自动从正文提取)
  • 封面图(复用现有图片选择器)
  • 正文(MDEditor)
  • 作者(选填)
  • 排序权重
  • 状态(草稿/发布)

6.6 内容采集页(ArticleCrawl.jsx)

复用现有 CrawlNews.jsx 的采集逻辑:

  • URL 输入框
  • 采集解析
  • 内容预览
  • 保存到 articles 表(而非 news_posts 表)

七、关键复用点

现有文件 复用内容 新文件
PostEdit.jsx MDEditor 编辑器、图片选择器、表单布局 ArticleEdit.jsx
CrawlNews.jsx URL 采集逻辑、内容解析 ArticleCrawl.jsx
news.py extract_summary()、generate_slug()、图片迁移逻辑 articles.py
CategoryList.jsx(news) 弹窗表单、列表展示、排序逻辑 ColumnList.jsx
api/index.js ApiClient 封装模式 新增 columnsApi、articlesApi

八、访客端预留接口(后续阶段开发)

为后续访客端开发预留以下 API 和页面设计:

预留 API

方法 路径 说明
GET /api/public/columns 获取启用的栏目树
GET /api/public/columns/{slug} 获取栏目详情
GET /api/public/articles 获取已发布内容列表(按栏目筛选)
GET /api/public/articles/{slug} 通过 slug 获取内容详情

预留页面路由

路由 说明
/column/:slug 栏目内容列表页
/article/:slug 内容详情页

九、实施阶段规划

阶段一:栏目管理模块

  1. backend/app/models/column.py - 栏目模型定义
  2. backend/app/routers/columns.py - 栏目管理 API
  3. backend/app/main.py - 注册栏目路由
  4. frontend/src/pages/admin/columns/ColumnList.jsx - 栏目列表页
  5. frontend/src/components/AdminLayout.jsx - 新增"栏目管理"导航
  6. frontend/src/App.jsx - 新增栏目管理路由
  7. frontend/src/api/index.js - 新增 columnsApi
  8. 测试验证

阶段二:内容管理模块

  1. backend/app/models/article.py - 内容模型定义
  2. backend/app/routers/articles.py - 内容管理 API
  3. backend/app/main.py - 注册内容路由
  4. frontend/src/pages/admin/articles/ArticleList.jsx - 内容列表页
  5. frontend/src/pages/admin/articles/ArticleEdit.jsx - 内容编辑页
  6. frontend/src/pages/admin/articles/ArticleCrawl.jsx - 内容采集页
  7. frontend/src/components/AdminLayout.jsx - 新增"内容管理"导航
  8. frontend/src/App.jsx - 新增内容管理路由
  9. frontend/src/api/index.js - 新增 articlesApi
  10. 测试验证