246 lines
7.7 KiB
Markdown
246 lines
7.7 KiB
Markdown
:::tip 文章管理
|
||
文章管理模块支持富文本编辑、分类关联、封面图片上传、图集管理等功能。适用于新闻、公告、帮助文档等内容管理场景。
|
||
:::
|
||
|
||
## 模块结构
|
||
|
||
```
|
||
application/article/
|
||
├── models.py # 文章模型
|
||
├── forms.py # 表单验证
|
||
├── services.py # 业务逻辑层(自定义函数)
|
||
├── views.py # 视图层
|
||
└── urls.py # 路由配置
|
||
```
|
||
|
||
:::warning 自定义 Service
|
||
文章模块因涉及文件处理、富文本清洗、图集解析等复杂逻辑,编写自定义 service 函数而非继承通用模式。
|
||
:::
|
||
|
||
## 文章模型
|
||
|
||
```python
|
||
# application/article/models.py
|
||
from application.models import BaseModel
|
||
|
||
class Article(BaseModel):
|
||
"""文章模型类"""
|
||
class Meta:
|
||
db_table = 'django_article'
|
||
|
||
category_id = models.IntegerField(null=True, blank=True, verbose_name='分类ID')
|
||
title = models.CharField(max_length=150, null=True, blank=True, verbose_name='文章标题')
|
||
cover = models.CharField(max_length=255, null=True, blank=True, verbose_name='文章封面')
|
||
images = models.TextField(null=True, blank=True, verbose_name='文章图集')
|
||
intro = models.CharField(max_length=255, null=True, blank=True, verbose_name='文章导读')
|
||
content = models.TextField(null=True, blank=True, verbose_name='文章内容')
|
||
author = models.CharField(max_length=100, null=True, blank=True, verbose_name='文章作者')
|
||
status = models.IntegerField(default=0, verbose_name='文章状态:0-正常 1-下架')
|
||
click = models.IntegerField(default=0, verbose_name='文章点击率')
|
||
sort = models.IntegerField(default=0, verbose_name='文章排序')
|
||
```
|
||
|
||
### 字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `category_id` | int | 所属分类 ID(关联 category 表) |
|
||
| `title` | str | 文章标题 |
|
||
| `cover` | str | 封面图片路径 |
|
||
| `images` | str | 图集路径(逗号分隔) |
|
||
| `intro` | str | 文章导读/摘要 |
|
||
| `content` | str | 富文本内容(HTML) |
|
||
| `author` | str | 文章作者 |
|
||
| `status` | int | 状态:0-正常 1-下架 |
|
||
| `click` | int | 点击次数(自动统计) |
|
||
| `sort` | int | 排序权重 |
|
||
|
||
## 文章状态流转
|
||
|
||
```
|
||
草稿(0) → 发布(0) → 下架(1) → 重新发布(0)
|
||
```
|
||
|
||
| 状态 | 说明 | 可执行操作 |
|
||
|------|------|-----------|
|
||
| 0-正常 | 文章已发布,前台可见 | 编辑、下架、删除 |
|
||
| 1-下架 | 文章已下架,前台不可见 | 编辑、重新发布、删除 |
|
||
|
||
## 文件处理机制
|
||
|
||
文章模块涉及三类文件字段,处理方式各不相同:
|
||
|
||
### 1. 封面图片(cover)-- save_file
|
||
|
||
```python
|
||
# 新增时
|
||
cover = save_file(data.cover, "article") if data.cover else None
|
||
```
|
||
|
||
`save_file(url, directory)` 的处理逻辑:
|
||
1. 将前端传来的 URL 还原为磁盘绝对路径
|
||
2. 若文件在临时目录(`temp/`),移动到正式目录(`upload/article/日期/`)
|
||
3. 返回相对路径存入数据库
|
||
|
||
### 2. 图集(images)-- _build_images / _parse_images
|
||
|
||
图集在数据库中存储为逗号分隔的相对路径字符串:
|
||
|
||
```
|
||
/article/20260907/a.png,/article/20260907/b.png
|
||
```
|
||
|
||
前端传入数组,后端拼接为字符串存储:
|
||
|
||
```python
|
||
def _build_images(images_list):
|
||
"""将图片列表拼接为逗号分隔的字符串"""
|
||
if not images_list:
|
||
return None
|
||
return ",".join([save_file(img, "article") for img in images_list])
|
||
|
||
def _parse_images(images_str):
|
||
"""将逗号分隔的字符串解析为完整 URL 列表"""
|
||
if not images_str:
|
||
return []
|
||
return [get_file_url(img) for img in images_str.split(",")]
|
||
```
|
||
|
||
### 3. 富文本内容(content)-- save_content
|
||
|
||
```
|
||
原始 HTML → 提取图片 → 迁移文件 → 替换路径 → 设置 alt → 存库
|
||
```
|
||
|
||
```python
|
||
from utils.common import save_content
|
||
|
||
# 处理富文本内容
|
||
content = save_content(content, title, "article")
|
||
```
|
||
|
||
## 添加文章
|
||
|
||
```python
|
||
def add_article(request):
|
||
"""添加文章"""
|
||
data, error = parse_request_body(request)
|
||
if error:
|
||
return R.failed(msg=error)
|
||
|
||
# 表单验证
|
||
form = forms.ArticleForm(data)
|
||
if not form.is_valid():
|
||
return R.failed(msg=regular.get_err(form))
|
||
|
||
try:
|
||
cleaned_data = form.cleaned_data
|
||
|
||
# 处理封面图片
|
||
cover = save_file(cleaned_data.get('cover'), "article") if cleaned_data.get('cover') else None
|
||
|
||
# 处理图集
|
||
images = _build_images(cleaned_data.get('images'))
|
||
|
||
# 处理富文本内容
|
||
content = save_content(cleaned_data.get('content'), cleaned_data.get('title'), "article")
|
||
|
||
# 创建文章
|
||
Article.objects.create(
|
||
category_id=cleaned_data.get('categoryId'),
|
||
title=cleaned_data.get('title'),
|
||
cover=cover,
|
||
images=images,
|
||
intro=cleaned_data.get('intro'),
|
||
content=content,
|
||
author=cleaned_data.get('author'),
|
||
status=cleaned_data.get('status', 0),
|
||
sort=cleaned_data.get('sort', 0),
|
||
create_user=get_username(request),
|
||
update_user=get_username(request)
|
||
)
|
||
return R.ok(msg="创建成功")
|
||
except Exception as e:
|
||
logging.error(f"添加文章异常: {str(e)}")
|
||
return R.failed(msg="添加失败,请稍后重试")
|
||
```
|
||
|
||
## 查询分页
|
||
|
||
### 过滤条件
|
||
|
||
| 参数 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `title` | 模糊搜索 | 文章标题 |
|
||
| `category_id` | 精确匹配 | 分类 ID |
|
||
| `status` | 精确匹配 | 文章状态 |
|
||
|
||
### 排序规则
|
||
|
||
按 `sort` 降序 -> `id` 降序。
|
||
|
||
### 响应数据
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"title": "DjangoAdmin 3.0 发布",
|
||
"cover": "http://domain/uploads/article/20260908/cover.png",
|
||
"images": ["http://domain/uploads/article/20260908/1.png"],
|
||
"intro": "DjangoAdmin 3.0 正式发布...",
|
||
"content": "<p>文章内容...</p>",
|
||
"author": "admin",
|
||
"status": 0,
|
||
"statusText": "正常",
|
||
"click": 100,
|
||
"sort": 1,
|
||
"categoryId": 1,
|
||
"categoryName": "产品动态",
|
||
"createUser": "admin",
|
||
"createTime": "2026-09-08 10:00:00"
|
||
}
|
||
```
|
||
|
||
## API 接口
|
||
|
||
| 接口 | 方法 | 权限节点 | 说明 |
|
||
|------|------|---------|------|
|
||
| `/article/page` | GET | `sys:article:page` | 分页查询 |
|
||
| `/article/detail/<int:article_id>` | GET | `sys:article:detail` | 详情查询 |
|
||
| `/article/add` | POST | `sys:article:add` | 新增文章 |
|
||
| `/article/update` | PUT | `sys:article:update` | 编辑文章 |
|
||
| `/article/status` | PUT | `sys:article:status` | 设置状态 |
|
||
| `/article/delete/<int:article_ids>` | DELETE | `sys:article:delete` | 删除文章(支持批量) |
|
||
|
||
## 前端实现
|
||
|
||
### 编辑弹窗
|
||
|
||
文章编辑弹窗包含:标题、分类选择、封面上传、图集上传、富文本编辑器、状态选择、排序。
|
||
|
||
```vue
|
||
<BasicModal @register="registerModal" @ok="handleSubmit" width="800px">
|
||
<BasicForm @register="registerForm" />
|
||
<!-- 富文本编辑器 -->
|
||
<Editor v-model="formData.content" :defaultConfig="editorConfig" />
|
||
</BasicModal>
|
||
```
|
||
|
||
### 文章列表
|
||
|
||
文章列表页支持按标题、分类、状态筛选,展示封面缩略图和状态标签。
|
||
|
||
## 总结
|
||
|
||
文章管理模块是项目中典型的「带文件处理」复杂业务模块,核心特点:
|
||
|
||
```
|
||
1. 自定义 Service:文件处理/富文本清洗/图集解析均为自定义逻辑
|
||
2. 三类文件字段:封面(save_file)、图集(逗号分隔)、富文本(save_content)
|
||
3. 临时文件迁移:前端上传到临时目录 -> 提交表单后后端迁移到正式目录
|
||
4. XSS 防护:富文本经 bleach 白名单清洗
|
||
5. 分类关联:通过 category_id 关联分类表
|
||
6. 状态管理:支持发布/下架状态切换
|
||
7. 点击统计:自动统计文章点击次数
|
||
```
|