签入版本

This commit is contained in:
2026-09-12 23:12:15 +08:00
commit 91998056ca
1841 changed files with 341325 additions and 0 deletions
@@ -0,0 +1,245 @@
:::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. 点击统计:自动统计文章点击次数
```