现有一个And Design的reactjs的后台管理web页面的项目,
在Chrome浏览器中点击一个下载链接时,会新建页面,弹框下载:

不希望弹框下载,而直接是普通的点击下载文件,类似于:

然后出现问题:
【已解决】ant design的reactjs去npm install时需要下载puppeteer的Chromium且速度很慢
然后去本地测试,结果出错:
然后再去解决此处的问题:
先去看看代码中,是如何弹框下载的
web前端代码调用逻辑是:
<code><a href="javascript:;" onClick={() => this.handleExport(record)} >导出</a>
</code>handleExport = (record) => {
const { dispatch } = this.props;
dispatch({
type: ‘script/scriptWordExport’,
payload: record.id,
});
};
export async function scriptWordExport(scriptID) {
return request(`${apiPrefix}/scripts/${scriptID}/script_word_export/`, {
headers: constructHeaders(),
});
}
Django后端的的api是:
<code> @detail_route(
methods=['get'],
url_path='script_word_export',
url_name='script_word_export',
permission_classes=[
AllowAny,
])
def script_word_export(self, request, pk=None):
script = self.get_object()
script_text = []
script_text.append('作者: ' + script.author.username)
...
file_dir = os.path.join(settings.BASE_DIR, 'apps', 'media')
file_name = script.title + '_' + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
document.save(file_dir + '/' + file_name + '.docx')
with open(file_dir + '/' + file_name + '.docx', 'rb') as f:
response = HttpResponse(f, content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document')
response['Content-Disposition'] = 'attachment; filename=' + 'script_' + file_name + '.docx'
response['Access-Control-Allow-Origin'] = '*'
return response
</code>看起来是:
后端返回HttpResponse
其中设置了content-type以及Content-Disposition
而之前自己的用过的Flask返回文件的话,则是用的是:
<code><-------------------------------------- # Flask API <-------------------------------------- def sendFile(fileBytes, contentType, outputFilename): """Flask API use this to send out file (to browser, browser can directly download file)""" return send_file( io.BytesIO(fileBytes), # io.BytesIO(fileObj.read()), mimetype=contentType, as_attachment=True, attachment_filename=outputFilename ) </code>
然后浏览器就可以直接下载到文件了。
就不会有弹框。
antd pro 点击下载文件
使用request请求文件下载的接口后不会下载文件,是不是被什么配置拦截了? · Issue #1456 · ant-design/ant-design-pro
请问excel导出也就是文件下载有处理办法吗 · Issue #543 · ant-design/ant-design-pro
File storage API | Django documentation | Django
python – Django Download File Url in Template – Stack Overflow
URL调度器 | Django documentation | Django
How to link to a downloadable PDF/DOC file? : Forums : PythonAnywhere
How do I make a download button in my web page work with Django? – Quora
django return file url
python – Having Django serve downloadable files – Stack Overflow
johnsensible/django-sendfile: xsendfile etc wrapper
很像我要的效果:
就是实现之前Flask中sendfile的效果
传入文件名和其他参数即可。
去试试
不过好像还是不够完美
先去看看:

<code>已拦截弹出式窗口 已拦截此网页上的下列弹出式窗口: 始终允许显示 的弹出式窗口 继续拦截弹出式窗口 </code>
Django chrome 文件下载 已拦截
Django chrome 文件下载 已拦截弹出式窗口
正常下载链接,被Google浏览器拦截 – Google Product Forums
Chrome提示下载文件是恶意文件已将其拦截怎么办_百度经验
html使用a标签不通过后台实现直接下载 – Martin的专栏 – SegmentFault 思否
reactjs a href download file
html5 – Download attribute in anchor tag in React component – Stack Overflow
就是前面说的:
<code><a target="_blank" href="your_url" download="filename.doc">点击下载文件</a> </code>
Improve handling of download attribute · Issue #1337 · facebook/react
How do I download a file when I click an HTML button? – Quora
HTML5 <a> Download Attribute – Sarah Bruce – Medium
Error when trying to download file with react-pdf – Questions – Prisma Forum
还是直接试试再说:
<code>➜ NaturlingCmsServer git:(master) ✗ pip3 install django-sendfile Collecting django-sendfile Downloading https://files.pythonhosted.org/packages/a3/01/2291deb21fe3036e16a22bb293f2bcbd095e05476f66d9d10eb4b44ff758/django-sendfile-0.3.11.tar.gz Requirement already satisfied: Django>=1.3 in /usr/local/lib/python3.6/site-packages (from django-sendfile) (2.0.6) Requirement already satisfied: pytz in /usr/local/lib/python3.6/site-packages (from Django>=1.3->django-sendfile) (2018.5) Building wheels for collected packages: django-sendfile Running setup.py bdist_wheel for django-sendfile ... done Stored in directory: /Users/crifan/Library/Caches/pip/wheels/da/02/7b/a3f430cc8f6a2a97e2d110d3a823e930f4e2e925e5bcc752c4 Successfully built django-sendfile Installing collected packages: django-sendfile Successfully installed django-sendfile-0.3.11 </code>
然后再去:
xxx/conf/development/settings.py
<code>SENDFILE_BACKEND = 'sendfile.backends.development' </code>
和
<code>from sendfile import sendfile return sendfile( request, full_file_name, attachment=True, attachment_filename=attachment_filename) </code>
结果:
问题依旧-》

看起来还是前端web端问题。
去试了试Chrome中,允许弹框,就可以
打开新的tab页面,然后下载到docx文件:

所以感觉还是:
<code><a href="javascript:;" onClick={() => this.handleExport(record)} >导出</a>
</code>的写法有问题
reactjs generate a href
javascript – How to create dynamic href in react render function? – Stack Overflow
不过,此处即使可以方便的生成要下载的docx文件的下载地址
比如:
http://localhost:65000/api/v1/scripts/7f3865f6-f1c4-4589-ae05-deedec983136/script_word_export/
但是也会有问题:
因为这个url需要传递header:
Authorization: JWT xxx
才能访问
而如果把url直接放到:
<a href=”此处的url”>
是没法点击下载的,缺少jwt的token。
不过,此处想到一个折中的,曲线救国的办法:
reactjs中,去下载到这个docx文件,然后保存到本地(浏览器内部临时保存),然后再去下载保存到用户的本地
reactjs a href but with token
但是好像有个问题:
需要用户操作两次才行:
第一次是点击获取文件,保存临时文件,更新a的hrefde 下载地址
然后再点击类似于之类的:
<code><a id="downloadSpeakAudio" download="" href="">下载录音文件<a/> </code>
才能下载文件
好像不方便点击一次就 内部生成url,并模拟点击a去触发下载
然后考虑可以:
内部生成下载的文件的url,并且返回到数据后,保存到临时文件中
把临时文件的地址放到a的href中,供下载
而为了避免需要用户再次点击才能下载,则想办法在js中模拟用户点击a的href,主动触发下载
从而实现:
用户点击了一次
(内部是先获取url并下载文件
再模拟点击下载)
开始下载(a的href的)文件
(而无需弹框)
reactjs download not window.open
reactjs download without window.open
reactjs – how can i handle an event to open a window in react js? – Stack Overflow
[SOLVED] Downloading files without window.open(), or opening a window iframepanel.
How to open a pdf downloaded from an API with JavaScript – Jayway
好像是可以试试reactjs中模拟a的click
Launch download in the same tab without opening new tab or window in Javascript – Stack Overflow
不过现在想到一个感觉更好的办法:
对于文件下载的url来说,可以考虑把token放到url的query string中
这样就可以直接把完整url放到a的href了,点击后就可以直接下载到文件了,
估计就不用弹框了。
去试试:
【已解决】Django中对于单个REST的接口把JWT的token验证放到query string的url中
【总结】
通过在Django中继承JSONWebTokenAuthentication,支持优先从query string中获取jwt的token,然后即可支持web前端传入url,可以解析url中的token了:
后端:
apps/util/jwt_token.py
<code># from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.authentication import get_authorization_header
import logging
logger = logging.getLogger('django')
class JWTAuthByQueryStringOrHeader(JSONWebTokenAuthentication):
# class JWTAuthByQueryStringOrHeader(BaseJSONWebTokenAuthentication):
"""
Extend the TokenAuthentication class to support querystring authentication
in the form of "http://www.example.com/?jwt_token=<token_key>"
"""
def get_jwt_value(self, request):
# Check if 'jwt_token' is in the request query params.
# Give precedence to 'Authorization' header.
logger.debug("request=%s", request)
queryParams = request.query_params
reqMeta = request.META
logger.debug("queryParams=%s", queryParams)
logger.debug("reqMeta=%s", reqMeta)
if ('jwt_token' in queryParams) and ('HTTP_AUTHORIZATION' not in reqMeta):
jwt_token = queryParams.get('jwt_token')
# got jwt token from query parameter
logger.debug("jwt_token=%s", jwt_token)
return jwt_token
else:
# call JSONWebTokenAuthentication's get_jwt_value
# to get jwt token from header of 'Authorization'
return super(JWTAuthByQueryStringOrHeader, self).get_jwt_value(request)
</code>然后再去更新配置,把之前的JSONWebTokenAuthentication改为JWTAuthByQueryStringOrHeader:
/conf/development/settings.py
<code># django-restframework and jwt settings
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'apps.util.jwt_token.JWTAuthByQueryStringOrHeader',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
...
}
</code>web前端:
保证a的href中直接传入对应的带jwt_token的url:
src/utils/token.js
<code>export function getEncodedToken() {
const token = localStorage.getItem('token')
if (token) {
return token
} else {
return ""
}
}
</code>src/services/api.js
<code>export function scriptWordExportUrl(scriptID, jwtToken) {
return `${apiPrefix}/scripts/${scriptID}/script_word_export/?jwt_token=${jwtToken}`;
}
</code>src/routes/Script/ScriptList.js
<code>import { scriptWordExportUrl } from '../../services/api';
import { getEncodedToken } from '../../utils/token';
// http://localhost:65000/api/v1/scripts/3d9e77b0-e538-49b8-8790-60301ca79e1d/script_word_export/?jwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMWVkMGEwZDgtMmFiYi00MDFkLTk5NTYtMTQ5MzcxNDIwMGUzIiwidXNlcm5hbWUiOiJsc2R2aW5jZW50IiwiZXhwIjoxNTMxOTAyOTU0LCJlbWFpbCI6InZpbmNlbnQuY2hlbkBuYXR1cmxpbmcuY29tIn0.wheM7Fmv8y8ysz0pp-yUHFqfk-IQ5a8n_8OplbYkj7s
const encodedJwtToken = getEncodedToken()
const exportScritpUrl = scriptWordExportUrl(record.id, encodedJwtToken)
return (
<Fragment>
...
<a href={exportScritpUrl} >导出</a>
...
</Fragment>
)
},
</code>然后点击导出,才能调用,url中带jwt_token,才能传入token到后台接口,用于拥有权限,才能下载导出文件:

而如果单独输入url,没有token的话,则是无法下载的:
<code>http://localhost:65000/api/v1/scripts/3d9e77b0-e538-49b8-8790-60301ca79e1d/script_word_export/ </code>
