feat: api 文件下载文件名识别忽略大小写同时支持前端指定 Close: #6177 (#8471)

This commit is contained in:
liaoxuezhi 2023-10-24 14:13:31 +08:00 committed by GitHub
parent b025e69e3d
commit 59d900ffd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 41 additions and 16 deletions

View File

@ -333,6 +333,8 @@ icon 也可以是 url 地址,比如
通过配置 `"actionType":"download"``api`,可以实现下载请求,它其实是 `ajax` 的一种特例,自动给 api 加上了 `"responseType": "blob"`
> 3.5.0 版本开始可以配置 `downloadFileName` 来覆盖下载文件名。注意:即便配置了 `downloadFileName`api 依然需要返回 `Content-Disposition` 头。
```schema: scope="body"
{
"label": "下载",

View File

@ -199,7 +199,7 @@ export default class PlayGround extends React.Component {
};
let response = await axios(config);
response = await attachmentAdpator(response, __);
response = await attachmentAdpator(response, __, api);
if (response.status >= 400) {
if (response.data) {

View File

@ -213,7 +213,7 @@ export function embed(
let response = config.mockResponse
? config.mockResponse
: await axios(config);
response = await attachmentAdpator(response, __);
response = await attachmentAdpator(response, __, api);
response = responseAdaptor(api)(response);
if (response.status >= 400) {

View File

@ -230,6 +230,7 @@ export interface ApiObject extends BaseApiObject {
) => ApiObject | Promise<ApiObject>;
/** 是否过滤为空字符串的 query 参数 */
filterEmptyQuery?: boolean;
downloadFileName?: string;
}
export type ApiString = string;
export type Api = ApiString | ApiObject;

View File

@ -5,24 +5,35 @@
* @returns
*/
export function attachmentAdpator(response: any, __: Function) {
import {ApiObject} from '../types';
export function attachmentAdpator(
response: any,
__: Function,
api?: ApiObject
) {
if (response && response.headers && response.headers['content-disposition']) {
const disposition = response.headers['content-disposition'];
let filename = '';
if (disposition && disposition.indexOf('attachment') !== -1) {
// disposition 有可能是 attachment; filename="??.xlsx"; filename*=UTF-8''%E4%B8%AD%E6%96%87.xlsx
// 这种情况下最后一个才是正确的文件名
let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
// 如果 api 中配置了,则优先用 api 中的配置
if (api?.downloadFileName) {
filename = api.downloadFileName;
} else {
// disposition 有可能是 attachment; filename="??.xlsx"; filename*=UTF-8''%E4%B8%AD%E6%96%87.xlsx
// 这种情况下最后一个才是正确的文件名
let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/i;
let matches = disposition.match(filenameRegex);
if (matches && matches.length) {
filename = matches[1].replace(`UTF-8''`, '').replace(/['"]/g, '');
}
let matches = disposition.match(filenameRegex);
if (matches && matches.length) {
filename = matches[1].replace(`UTF-8''`, '').replace(/['"]/g, '');
}
// 很可能是中文被 url-encode 了
if (filename && filename.replace(/[^%]/g, '').length > 2) {
filename = decodeURIComponent(filename);
// 很可能是中文被 url-encode 了
if (filename && filename.replace(/[^%]/g, '').length > 2) {
filename = decodeURIComponent(filename);
}
}
let type = response.headers['content-type'];

View File

@ -33,6 +33,7 @@ export function handleAction(
action.actionType = 'ajax';
const api = normalizeApi((action as any).api);
api.responseType = 'blob';
api.downloadFileName = action.downloadFileName;
(action as any).api = api;
}

View File

@ -10,7 +10,8 @@ export const env: RenderOptions = {
jumpTo: () => {
toast.info('温馨提示:预览模式下禁止跳转');
},
fetcher: async ({url, method, data, config, headers}: any) => {
fetcher: async (api: any) => {
let {url, method, data, config, headers} = api;
config = config || {};
config.url = url;
config.withCredentials = true;
@ -40,7 +41,7 @@ export const env: RenderOptions = {
}
let response = await axios(config);
response = await attachmentAdpator(response, (msg: string) => '');
response = await attachmentAdpator(response, (msg: string) => msg, api);
return response;
},
isCancel: (value: any) => (axios as any).isCancel(value),

View File

@ -617,6 +617,11 @@ export interface SchemaApiObject {
* autoFill
*/
silent?: boolean;
/**
*
*/
downloadFileName?: string;
}
export type SchemaApi = string | SchemaApiObject;

View File

@ -198,6 +198,7 @@ export interface DownloadActionSchema
*
*/
actionType: 'download';
downloadFileName?: string;
}
export interface SaveAsActionSchema
@ -362,6 +363,7 @@ export interface OtherActionSchema extends ButtonSchema {
export interface VanillaAction extends ButtonSchema {
actionType?: string;
downloadFileName?: string;
}
/**
@ -426,7 +428,8 @@ const ActionProps = [
'requireSelected',
'countDown',
'fileName',
'isolateScope'
'isolateScope',
'downloadFileName'
];
import {filterContents} from './Remark';
import {ClassNamesFn, themeable, ThemeProps} from 'amis-core';
@ -624,6 +627,7 @@ export class Action extends React.Component<ActionProps, ActionState> {
action.actionType = 'ajax';
const api = normalizeApi((action as AjaxActionSchema).api);
api.responseType = 'blob';
api.downloadFileName = action.downloadFileName;
(action as AjaxActionSchema).api = api;
}