Files
djangoadmin-wiki/DjangoAdmin_Django_AntdVue/zh/volume4/business/article.md
T
2026-09-12 23:12:15 +08:00

246 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:::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. 点击统计:自动统计文章点击次数
```