文档下载功能
一、文档下载功能需求分析
1.功能
- 文档下载展示页
- 文档列表
- 文档下载
二、模型设计
1.字段分析
- 文件url
- 文件名
- 文件标题
- 简介
- 封面图片url
2.模型定义
# 在doc/models.py中定义如下模型
from django.db import models
from utils.models import BaseModel
class Doc(BaseModel):
"""
文件模型
"""
file_url = models.URLField('文件url', help_text='文件url')
file_name = models.CharField('文件名', max_length=48, help_text='文件名')
title = models.CharField('文件标题', max_length=150, help_text='文件标题')
desc = models.TextField('文件描述', help_text='文件描述')
image_url = models.URLField('封面图片url', help_text='封面图片url')
author = models.ForeignKey('user.User', on_delete=models.SET_NULL, null=True)
class Meta:
db_table = 'tb_docs' # 数据库表名
verbose_name = '文件表' # admin 站点中显示的名称
verbose_name_plural = verbose_name
def __str__(self):
return self.title
三、文档下载页面
1.接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /doc/download/ |
参数格式 | 无参数 |
-
返回结果:
文档下载页面
3.后端代码
-
视图
# 在doc/views.py里编写如下视图 from .models import Doc class DocView(View): """ 文档下载页面视图 """ def get(self, request): return render(request, 'doc/docDownload.html')
-
路由
# 在doc/urls.py中添加如下路由 from django.urls import path from . import views app_name = 'doc' urlpatterns = [ path('download/', views.DocView.as_view(), name='index'), ]
# 在根urls.py中添加如下路由 path('doc/', include('doc.urls'))
4.前端代码
- html
<!-- template/doc/docDownload.html -->
{% extends 'base/base.html' %}
{% load static %}
{% block title %}文档下载{% endblock %}
{% block link %}
<link rel="stylesheet" href="{% static 'css/doc/docDownload.css' %}">
<script>
iMenuIndex = 2
</script>
{% endblock %}
{% block main_contain %}
<!-- main-contain start -->
<div class="main-contain ">
<div class="banner">
<img src="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1802845035,3786939119&fm=26&gp=0.jpg"
alt="">
</div>
<div class="pay-doc-contain">
<ul class="pay-list">
<!-- doc info -->
</ul>
</div>
<!-- btn-more start -->
<a href="javascript:void(0);" class="btn-more">滚动加载更多</a>
<!-- btn-more end -->
</div>
<!-- main-contain end -->
{% endblock %}
{% block script %}
<script src="{% static 'js/doc/doc.js' %}"></script>
{% endblock %}
-
css
/* 在static/css/doc/docDownload.css中,把如下代码覆盖之前的样式 */ /* ================= main start ================= */ #main { margin-top: 25px; min-height: 700px; flex: 1; } /* ========= main-contain start ============ */ #main .main-contain { width: 800px; float: left; margin-bottom: 30px; } /* ========= banner start =========== */ .main-contain .banner { width: 100%; } .main-contain .banner img { max-width: 100%; } .main-contain .pay-doc-contain { background: #fff; } .main-contain .pay-doc-contain .pay-list { display: flex; justify-content: space-between; flex-flow: wrap; padding: 0 20px 20px; } .main-contain .pay-doc-contain .pay-item { width: 800px; height: 200px; border-top: 1px solid #ddd; margin-top: 20px; display: flex; } .main-contain .pay-doc-contain .pay-item:hover { box-shadow: 2px 2px 2px #ccc; } .pay-doc-contain .pay-item .pay-img { width: 120px; margin-right: 30px; } .pay-doc-contain .pay-item .pay-contain { width: 250px; position: relative } .pay-item .pay-contain .pay-title { font-size: 20px; line-height: 40px; white-space: nowrap; overflow: hidden; } .pay-item .pay-contain .pay-desc { line-height: 20px; color: #878787; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; font-size: 14px; overflow: hidden; } .pay-item .pay-price { display: block; font-size: 20px; text-align: right; padding-right: 20px; color: coral; } .d-contain { width: 100%; margin: 20px 0 20px ; font-size: 18px; line-height: normal; } .d-contain .doc-desc { text-indent: 2em; margin: 15px; /*padding: 10px;*/ } .d-contain .doc-title { font-size: 20px; font-weight: bold; color: chocolate; } .btn-more { display: block; border: none; width: 400px; line-height: 40px; text-align: center; background: skyblue; color: #efefef; margin: 20px auto; } /* ========= banner end =========== */ /* ========= main-contain end ============ */ /* ================= main end ================= */
四、文档列表
1. 接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /doc/docs/ |
参数格式 | 查询参数 |
- 参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
page | 整数 | 否 | 页码 |
- 返回结果:
{
"errno": "0",
"errmsg": "OK",
"data": {
"total_page": 2,
"docs": [
{
"desc": "youkou老师说:每天一个单词,充实每一天!",
"file_name": "django项目班_英语单词2.doc",
"file_url": "/media/django项目班_英语单词2.doc",
"image_url": "/media/django项目班_英语单词.jpg",
"title": "django项目班_英语单词2"
},
{
"desc": "本书由奋战在Python开发一线近20年的Luciano Ramalho执笔,从语言设计层面剖析编程细节,兼顾Python 3和Python 2,教你写出风格地道的Python代码。",
"file_name": "流畅的Python.pdf",
"file_url": "/media/流畅的Python.pdf",
"image_url": "/media/fluent_python_1.jpg",
"title": "流畅的Python"
}
]
}
}
2.后端代码
-
视图
# 在doc/views.py中添加DocListView视图如下 class DocListView(View): def get(self, request): # 1.拿到所有文档 docs = Doc.objects.values('file_url', 'file_name', 'title', 'desc', 'image_url').filter(is_delete=False) # 2.分页 paginator = Paginator(docs, constants.PER_PAGE_DOC_COUNT) try: page = paginator.get_page(int(request.GET.get('page'))) except Exception as e: page = paginator.get_page(1) data = { 'total_page': paginator.num_pages, 'docs': list(page) } return json_response(data=data)
-
常量配置
# 在doc文件夹下创建constants.py文件,配置如下常量 PER_PAGE_DOC_COUNT = 5
-
导入数据
# 将给定的media文件夹内容复制到项目media文件内 # 然后导入tb_docs.sql中的数据 mysql -uroot -pqwe123 -D tzproject < tb_docs.sql
3. 前端代码
-
js代码
// 创建static/js/doc/doc.js文件,代码如下 $(() => { let iPage = 1; // 当前页面页数 let iTotalPage = 1; // 总页数 let bIsLoadData =false; // 是否正在加载 fn_load_docs(); // 加载文件列表 // 页面滚动加载 $(window).scroll(function () { // 浏览器窗口高度 let showHeigtht = $(window).height(); // 整个网页高度 let pageHeight = $(document).height(); //页面可以滚动的距离 let canScrollHeight = pageHeight - showHeigtht; // 页面滚动了多少, 整个是随着页面滚动实时变化的 let nowScroll = $(document).scrollTop(); if ((canScrollHeight - nowScroll) < 100){ if(!bIsLoadData){ bIsLoadData = true; //判断页数,去更新新闻,小于总数才加载 if(iPage < iTotalPage){ iPage += 1; fn_load_docs(); }else { message.showInfo('已全部加载,没有更多内容!'); $('a.btn-more').html('已全部加载,没有更多内容!') } } } }); // 获取docs信息 function fn_load_docs() { $ .ajax({ url: '/doc/docs/', type: 'GET', data: {page: iPage}, dataType: 'json' }) .done((res) => { if (res.errno === '0') { iTotalPage = res.data.total_page; res.data.docs.forEach((doc) => { let content = `<li class="pay-item"> <div class="pay-img doc"></div> <img src="${ doc.image_url }" alt="" class="pay-img doc"> <div class="d-contain"> <p class="doc-title">${ doc.title }</p> <p class="doc-desc">${ doc.desc }</p> <!-- /www/?xxx --> <a href="${ doc.file_url }" class="pay-price" download="${ doc.file_name }">下载</a> </div> </li>`; $('.pay-list').append(content); bIsLoadData = false; $('a.btn-more').html('滚动加载更多'); }) } else { message.showError(res.errmsg) } }) .fail(() => { message.showError('服务器超时,请重试!') }) } });
五、文档下载
其实是上面通过前端a标签我们已经实现了本项目中的文件下载。但是实际项目中经常出现的文件下载功能中的文件是动态生成的,这种情况怎么处理呢?
# 文件下载视图
class DocDownload(View):
"""
"""
def get(self, request, doc_id):
file_fb = makefile() # 生成文件流
try:
res = FileResponse(file_fb)
except Exception as e:
logger.info("获取文档内容出现异常:\n{}".format(e))
raise Http404("文档下载异常!")
ex_name = 'xls' # 文件后缀,表明文件类型
# https://stackoverflow.com/questions/23714383/what-are-all-the-possible-values-for-http-content-type-header
# http://www.iana.org/assignments/media-types/media-types.xhtml#image
if not ex_name:
raise Http404("文档url异常!")
else:
ex_name = ex_name.lower()
if ex_name == "pdf":
res["Content-type"] = "application/pdf"
elif ex_name == "zip":
res["Content-type"] = "application/zip"
elif ex_name == "doc":
res["Content-type"] = "application/msword"
elif ex_name == "xls":
res["Content-type"] = "application/vnd.ms-excel"
elif ex_name == "docx":
res["Content-type"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
elif ex_name == "ppt":
res["Content-type"] = "application/vnd.ms-powerpoint"
elif ex_name == "pptx":
res["Content-type"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
else:
raise Http404("文档格式不正确!")
doc_filename = escape_uri_path('某表格.xls')
res["Content-Disposition"] = "attachment; filename*=UTF-8''{}".format(doc_filename)
return res