0、在项目上遇到一个需要导出pdf的需求,做一下记录。
1、项目截图:
2、相关代码:
TestController:
package xyz.haijin.controller;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import xyz.haijin.utils.FileUtil;
import xyz.haijin.utils.PDFUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @author haijin
* @date 2024/8/20 16:37
* @describe 测试
*/
@RestController
@RequestMapping("/")
public class TestController {
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
public static final String CONTENT_DISP = "Content-disposition";
@RequestMapping(value = "/test", method = RequestMethod.GET)
public void test(HttpServletResponse response) {
response.setContentType("application/octet-stream");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setHeader(CONTENT_DISP, "test.pdf");
try {
exportApiPdf(response);
} catch (Exception e) {
logger.info("导出异常【{}】",e.getMessage(),e);
}
}
/**
* 导出pdf
* @param httpServletResponse ignore
*/
private void exportApiPdf(final HttpServletResponse httpServletResponse) throws Exception {
OutputStream os = httpServletResponse.getOutputStream();
// 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例
Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
StringTemplateLoader stringLoader = new StringTemplateLoader();
// 获取数据
Map<String, Object> dataMap = getMapData();
// 格式化整体 api文档数据
InputStream templateIs = this.getClass().getClassLoader().getResourceAsStream("assets/templates/template_test_document.ftl");
String templateString = FileUtil.readString(templateIs);
stringLoader.putTemplate("template_test_document", templateString);
cfg.setTemplateLoader(stringLoader);
Template tpl = cfg.getTemplate("template_test_document", "utf-8");
StringWriter writer = new StringWriter();
// 将数据输出到html中
tpl.process(dataMap, writer);
String html = writer.toString().replace("/*","/ *");
writer.flush();
// 把html代码传入PDF中
byte[] bytes = PDFUtils.createByString(html);
os.write(bytes);
}
/**
* 模拟获取数据
* @return ignore
*/
private Map<String,Object> getMapData(){
Map<String,Object> data = new HashMap<>(3);
data.put("testName","测试");
data.put("testValue","显示的内容");
data.put("testBool",true);
return data;
}
}
FileUtil:
package xyz.haijin.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.haijin.controller.TestController;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
* @author haijin
* @description: 文件工具类
* @date 2024/8/20 17:09
*/
public class FileUtil {
private static final Logger log = LoggerFactory.getLogger(FileUtil.class);
/**
* 读取字符串
* @param stream 输入流
* @return ignore
*/
public static String readString(InputStream stream) {
StringBuilder sb = new StringBuilder();
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
String s = null;
while ((s = br.readLine()) != null) {
sb.append(s);
}
br.close();
return sb.toString();
} catch (Exception e) {
log.info(e.getMessage());
return null;
} finally {
if (br != null) {
try {
br.close();
} catch (Exception e) {
log.info(e.getMessage());
}
}
}
}
}
PDFUtils:
package xyz.haijin.utils;
import com.lowagie.text.*;
import com.lowagie.text.html.simpleparser.HTMLWorker;
import com.lowagie.text.html.simpleparser.StyleSheet;
import com.lowagie.text.pdf.*;
import com.lowagie.text.pdf.events.PdfPageEventForwarder;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author haijin
* @description: pdf工具类
* @date 2024/8/20 17:13
*/
public class PDFUtils {
/**
* 通过字符串生成pdf文档
* @param content 内容
* @return ignore
* @throws Exception 异常
*/
public static byte[] createByString(String content) throws Exception {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
writeToOutPutStreamAsPdf(content, bos);
return bos.toByteArray();
}
}
/**
* 创建pdf
* @param htmlStr 字符串
* @param os 字节流
* @throws Exception 异常
*/
private static void writeToOutPutStreamAsPdf(String htmlStr, ByteArrayOutputStream os) throws Exception {
final StringReader reader = new StringReader(htmlStr);
final StyleSheet styleSheet = new StyleSheet();
final Map<String, Object> interfaceProps = new HashMap<>();
final List<Element> elements = HTMLWorker.parseToList(reader, styleSheet, interfaceProps);
//定义pdf文件尺寸,采用A4横切
// 左、右、上、下间距
Document document = new Document(PageSize.A4, 25, 25, 15, 40);
//定义输出路径
PdfWriter writer = PdfWriter.getInstance(document, os);
PdfPageEventForwarder header = new PdfPageEventForwarder();
writer.setPageEvent(header);
writer.addViewerPreference(PdfName.PRINTSCALING, PdfName.NONE);
document.open();
// 字体
BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
Font fontChinese = new Font(bfChinese, 12, Font.NORMAL);
// 边框
Rectangle border = new Rectangle(0f, 0f);
border.setBorderWidthLeft(1f);
border.setBorderWidthBottom(1f);
border.setBorderWidthRight(1f);
border.setBorderWidthTop(1f);
// 设置
for (Element element : elements) {
if (element instanceof Paragraph) {
for (Element chunkElement : element.getChunks()) {
if (chunkElement != null) {
Chunk chunk = (Chunk) chunkElement;
chunk.setFont(fontChinese);
}
}
}
if (element instanceof PdfPTable) {
PdfPTable pdfTable = (PdfPTable) element;
for (PdfPRow row : pdfTable.getRows()) {
if (row != null) {
for (PdfPCell cell : row.getCells()) {
if (cell != null) {
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_LEFT);
cell.cloneNonPositionParameters(border);
cell.setUseBorderPadding(true);
cell.setPadding(0f);
if (cell.getColumn() != null && cell.getCompositeElements() != null) {
for (Object object : cell.getCompositeElements()) {
if (object != null) {
for (Element chunkElement : ((Element) object).getChunks()) {
if (chunkElement != null) {
Chunk chunk = (Chunk) chunkElement;
chunk.setFont(fontChinese);
}
}
}
}
}
}
}
}
}
}
document.add(element);
}
document.close();
}
}
Application:
package xyz.haijin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author haijin
* @date 2024/8/20 16:37
*/
@SpringBootApplication(scanBasePackages = {"xyz.haijin"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
template_test_document.ftl:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="Content-Style-Type" content="text/css"/>
<meta name="generator" content="Aspose.Words for .NET 15.1.0.0"/>
<title></title></head>
<body>
<div><p style="margin:0pt; orphans:0; text-align:center; widows:0"><span style="font-family:楷体-简; font-size:16pt">[${testName}] 测试pdf</span>
<br/>
</p>
<div style="text-align:center">
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse; margin:0 auto; width:546.55pt">
<tr style="height:31.7pt">
<td colspan="5"
style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:88.5pt">
<p style="margin:0pt; orphans:0; text-align:center; widows:0"><span
style="font-family:等线; font-size:10.5pt">test 名称: </span></p></td>
<td colspan="7"
style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:435.7pt">
<p style="margin:0pt; orphans:0; text-align:left; margin-left: 3pt; widows:0"><span
style="font-family:等线; font-size:10.5pt">${testName?default('')} </span></p></td>
</tr>
<tr style="height:31.7pt">
<td colspan="5"
style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:88.5pt">
<p style="margin:0pt; orphans:0; text-align:center; widows:0"><span
style="font-family:等线; font-size:10.5pt">描述: </span></p></td>
<td colspan="7"
style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:435.7pt">
<p style="margin:0pt; orphans:0; text-align:left; margin-left: 3pt; widows:0"><span
style="font-family:等线; font-size:10.5pt">${testValue?default('')} </span></p></td>
</tr>
<tr style="height:31.7pt">
<td colspan="5"
style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:88.5pt">
<p style="margin:0pt; orphans:0; text-align:center; widows:0"><span
style="font-family:等线; font-size:10.5pt">是否必须: </span></p></td>
<td colspan="7"
style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:435.7pt">
<p style="margin:0pt; orphans:0; text-align:left; margin-left: 3pt; widows:0"><span
style="font-family:等线; font-size:10.5pt">${testBool?string('是', '否')} </span></p></td>
</tr>
</table>
</div>
</div>
</body>
</html>
application.yml:
server:
port: 8081 #提供者的端口
logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-logs}/}spring.log}"/>
<springProfile name="!local">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>30</maxHistory>
<maxFileSize>100MB</maxFileSize>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</springProfile>
<springProfile name="local">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
</configuration>
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.haijin</groupId>
<artifactId>pdf</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.compilerVersion>8</maven.compiler.compilerVersion>
<spring-boot.version>2.6.7</spring-boot.version>
<open-pdf.version>1.3.29</open-pdf.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>${open-pdf.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>${spring-boot.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>appassembler-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3、导出的结果:
4、vue下载:
request.js:
import axios from 'axios'
import { Message } from 'element-ui'
import QS from 'qs'
import store from '@/store'
import user from '@/utils/services/user'
import { logout } from '@/api/common'
import { getQueryString } from '@/utils/index'
const axiosTest = axios.create({
withCredentials: true,
timeout: 120000,
})
axiosTest.interceptors.request.use(
(config) => {
config.headers.operationModule = encodeURI(store.state.operationModule)
config.headers.logFlag = store.state.flag
store.commit('SET_FLAG', 0)
config.headers['Content-type'] = 'application/json; charset=utf-8'
if (config.method === 'get')
config.data = true
if (getQueryString('access_token')) {
config.headers.Authorization = `Bearer ${getQueryString('access_token')}`
}
return config
},
(error) => {
console.log || console.log(JSON.stringify(error))
return Promise.reject(error)
},
)
axiosTest.interceptors.response.use(
(response) => {
// const reg = RegExp(/token已失效|token 已失效|token 已注销/)
const reg = /token已失效|token 已失效|token 已注销/
if (response.data.code === undefined && response.data !== 'success') {
if (response.request.responseType !== 'blob') {
const errorMsg = JSON.stringify(response.data)
const message = '返回数据失败啦!!错误信息【{0}】'.format(
errorMsg.length > 300 ? errorMsg.substr(0, 300) : errorMsg,
)
errorMessageHandle(message)
}
}
else if (
response.data.code !== '0'
&& !response.config.url.includes('test/config/refresh')
) {
if (
!response.config.url.includes('/test')
&& reg.test(response.data.message)
) {
if (response.data.message.includes('token')) {
window.localStorage.setItem('invalid_token', response.data.message)
window.location.href = '/'
}
else {
logout(getQueryString('access_token')).then(() => {
goError(response.data.message, response.data.code)
})
}
}
if (response.data.code === '-1')
return response
// 去除接口超时提示
if (!response.data.message.includes('Read timed out')) {
errorMessageHandle(
'返回数据失败啦!!错误码=【{0}】, 错误信息=【{1}】'.format(
response.data.code,
response.data.message,
),
)
}
// return Promise.reject(new Error('error'));
return Promise.reject(response.data.message || 'error')
}
return response
},
(error) => {
const errorMsg = JSON.stringify(error.response.data || error.response || error)
if (error.response.status === 401) {
errorMessageHandle(
'返回数据异常啦!!错误信息【{0}】'.format(
errorMsg.length > 300 ? errorMsg.substr(0, 300) : errorMsg,
),
)
goError(escape(error.response.data.message), error.response.data.code)
}
else {
errorMessageHandle(
'返回数据异常啦!!错误信息【{0}】'.format(
errorMsg.length > 300 ? errorMsg.substr(0, 300) : errorMsg,
),
)
}
console.log || console.log(JSON.stringify(error))
return Promise.reject(error)
},
)
export function goError(message, code) {
document.cookie = `message=${message}`
document.cookie = `code=${code}`
user.gotoLogin('error.html')
}
export function download(downloadUrl, data, filename = '下载数据', vue, type, flag) {
if (type === null || type === undefined)
type = 'application/vnd.ms-excel,charset=utf-8'
if (flag === '123') {
const url = store.state.service.apiUrlHost + downloadUrl
return axios({
method: 'post',
url,
data,
headers: { Authorization: `Bearer ${getQueryString('access_token')}` },
responseType: 'arraybuffer',
}).then((res) => {
let errorObject = null
let isFaild = false
const enc = new TextDecoder('utf-8')
const errorMessage = enc.decode(new Uint8Array(res.data))
if (errorMessage.includes('message')) {
errorObject = JSON.parse(errorMessage)
isFaild = true
}
if (isFaild) {
errorMessageHandle(`下载失败[${errorObject.message}]`)
}
else {
const blob = new Blob([res.data], { type })
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = filename
link.click()
}
})
}
else {
const url = store.state.service.apiUrlHost + downloadUrl
return axios({
method: 'post',
url,
data,
headers: { Authorization: `Bearer ${getQueryString('access_token')}` },
responseType: 'arraybuffer',
}).then((res) => {
let errorObject = null
const enc = new TextDecoder('utf-8')
const errorMessage = enc.decode(new Uint8Array(res.data))
const isFaild
= Object.hasOwnProperty.call(res.data, 'message')
|| Object.hasOwnProperty.call(res.data, 'msg')
if (errorMessage.includes('message')) {
errorObject = JSON.parse(errorMessage)
errorMessageHandle(`下载失败[${errorObject.message}]`)
}
else if (isFaild) {
errorMessageHandle(`下载失败[${res.data.message}]`)
}
else {
const blob = new Blob([res.data], { type })
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = filename
link.click()
if (vue !== null && vue !== undefined) {
vue.exportLoading = false
if (type === null || type === undefined)
vue.exportName = '下载文档'
else
vue.exportName = '导出'
}
}
})
}
}
// 导出为word
export function downloadWord(downloadUrl, data, filename = '下载数据', vue, type) {
if (type === null || type === undefined)
type = 'application/msword,charset=utf-8'
const url = store.state.service.apiUrlHost + downloadUrl
return axios({
method: 'post',
url,
data,
headers: { Authorization: `Bearer ${getQueryString('access_token')}` },
responseType: 'arraybuffer',
}).then((res) => {
const blob = new Blob([res.data], { type })
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = filename
link.click()
if (vue !== null && vue !== undefined) {
vue.exportLoading = false
if (type === null || type === undefined)
vue.exportName = '下载文档'
else
vue.exportName = '导出'
}
})
}
/**
* 下载cert
* @param {String} downloadUrl 下载链接
* @param {Object} params 提交参数
* @param {String} filename 保存的文件名,默认:文件.zip
* @param {String} type MIME 类型,默认:application/zip
* @returns Promise
*/
export function downloadCert(downloadUrl, params, filename = '文件', type) {
const url = `${store.state.service.apiUrlHost}${downloadUrl}`
if (!type)
type = 'application/json,application/zip'
return axios({
method: 'GET',
url,
params,
headers: { Authorization: `Bearer ${getQueryString('access_token')}` },
responseType: 'arraybuffer',
}).then((res) => {
if (res.headers['content-type'])
type = res.headers['content-type']
if (res.headers['content-type'] === 'application/json')
throw new Error('下载失败')
const blob = new Blob([res.data], { type })
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = filename
link.click()
})
}
export function uploadFile(content) {
console.log('myUpload...')
console.log(content)
return axios({
method: 'post',
url: content.action,
timeout: 20000,
data: content.file,
})
.then(() => {
content.onSuccess('文件上传成功')
})
.catch((error) => {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
content.onError(`文件上传失败(${error.response.status}),${error.response.data}`)
}
else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
content.onError('文件上传失败,服务器端无响应')
}
else {
// Something happened in setting up the request that triggered an Error
content.onError('文件上传失败,请求封装失败')
}
})
}
/**
* 文件格式不确定的文件下载
* @param {String} downloadUrl 下载链接
* @param {Object} params 提交参数
* @param {String} name 保存的文件名
* @returns Promise
*/
export function downloadVariousDocuments(downloadUrl, method, params, name = '下载数据') {
const url = `${store.state.service.apiUrlHost}${downloadUrl}`
return axios({
url,
method,
params,
headers: { Authorization: `Bearer ${getQueryString('access_token')}` },
responseType: 'blob',
})
.then((res) => {
if (res.data.type === 'application/json') {
// 判断数据来源是Blob类型还是json类型
// 如果是json类型,使用FileReader来获取里面的信息
// FileReader 一般处理 文件流信息
const reader = new FileReader()
reader.onload = (event) => {
const result = JSON.parse(event.target.result)
if (!result.success)
Message.error(result.message || '下载失败')
}
reader.readAsText(res.data)
}
else {
// 数据来源是blob 直接下载
const filename = name === '文件样例' ? res.headers['content-disposition'].split(';')[1].split('=')[1] : `${name}.${res.headers['content-disposition'].split(';')[1].split('.')[1]}`
const blob = new Blob([res.data])
const Temp = document.createElement('a')
Temp.href = window.URL.createObjectURL(blob)
Temp.download = filename
document.body.appendChild(Temp)
Temp.click()
document.body.removeChild(Temp)
window.URL.revokeObjectURL(Temp)
}
})
.catch(() => {
Message.error('接口返回失败')
})
}
function errorMessageHandle(message) {
messageHandle(message, 'error')
}
function messageHandle(message, type) {
Message({
message,
type,
duration: 3000,
customClass: 'message-override',
})
}
export function qsStringify(data) {
return QS.stringify(data)
}
export default axiosTest
5、ftl语法大全:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Freemarker 语法大全</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style>
html {
font-size: 14px;
font-weight: 400;
}
.exp {
font-size: 12px;
color: lightgray;
}
</style>
</head>
<body>
<p>当前时间:${.now?string("yyyy-MM-dd HH:mm:ss.sss")}</p>
<dl>
<dt>list长度:<span class="exp">${list?size}</span></dt>
<dt>列表</dt>
<#list list as item>
<dd>${item }, 索引:${item_index },hasNext:${item_has_next}</dd>
</#list>
<dt>数字遍历</dt>
<#list 1..3 as item>
<dd>数字${item}</dd>
</#list>
<dt>map</dt>
<#list map?keys as key>
<dd>${map[key]}, 索引:${key_index },hasNext:${key_has_next}</dd>
</#list>
</dl>
<dl>
<dt>字符串</dt>
<dd>普通字符串:<span class="exp">${name}</span></dd>
<dd>非html编码:<span class="exp">${htmlText}</span></dd>
<dd>html编码:<span class="exp">${htmlText?html}</span></dd>
<dd>首字母大写:<span class="exp">${name?cap_first}</span></dd>
<dd>首字母小写:<span class="exp">${name?uncap_first}</span></dd>
<dd>全小写:<span class="exp">${name?lower_case}</span></dd>
<dd>全大写:<span class="exp">${name?upper_case}</span></dd>
<dd>去除首位空格:<span class="exp">${name?trim}</span></dd>
<dd>空字符串:<span class="exp">${null?if_exists}</span></dd>
<dd>是否包含某个字符串:<span class="exp">${name?contains("wWw")?string}</span></dd>
<dd>默认值:<span class="exp">${null?default("空值默认")}</span></dd>
<dd>“${name}”字符串长度:<span class="exp">${name?length}</span></dd>
<dd>定义字符串:<span class="exp">str=码一码<#assign str="码一码"/></span></dd>
<dd>字符串拼接(1):<span class="exp">${"字符串拼接 + " + str}</span></dd>
<dd>字符串拼接(2):<span class="exp">${"字符串拼接 + ${str}"}</span></dd>
<dd>字符串截取单个字符(1):<span class="exp">${str[1]}</span></dd>
<dd>字符串截取(2):<span class="exp">${str?substring(1)}</span></dd>
<dd>字符串截取(3):<span class="exp">${str?substring(1,2)}</span></dd>
<dd>indexOf:<span class="exp">${str?index_of("一")}</span></dd>
<dd>split分割字符串:<span class="exp">
<#list "a|b|c"?split("|") as item>
${item}
</#list>
</span></dd>
<dd>if...elseif...else:<span class="exp">
<#if null == ''>
匹配if显示
<#elseif null == '1'>
匹配elseif显示
<#else>
匹配else显示
</#if></span>
</dd>
</dl>
<dl>
<dt>switch</dt>
<dd>
<#switch str>
<#case "你好">
匹配“你好”
<#break >
<#case "码一码">
匹配“码一码”
<#break >
<#default>
默认匹配
</#switch>
</dd>
</dl>
<dl>
<dt>数字</dt>
<dd>普通数字:<span class="exp">${num}</span></dd>
<dd>数字类型:<span class="exp">${num?string.number}</span></dd>
<dd>货币类型:<span class="exp">${num?string.currency}</span></dd>
<dd>百分比类型:<span class="exp">${num?string.percent}</span></dd>
<dd>格式化数字:<span class="exp">${num?string("#.###")}</span></dd>
<dd>取数字的整数部分:<span class="exp">${num?int}</span></dd>
</dl>
<dl>
<dt>运算符</dt>
<dd>不等于:!= <span class="exp">例如:${(1 != 2)?string('1 != 2', '1 == 2')}</span></dd>
<dd>等于:== <span class="exp">例如:${(1 == 1)?string('1 == 1', '1 != 1')}</span></dd>
<dd>大于(1):> <span
class="exp">例如:${(2 > 1)?string('2 > 1', '2 < 1')}。<strong>注:使用> 时必须加括号,否则可能会被当成普通的标签闭合符号而引起报错</strong></span>
</dd>
<dd>大于(2):gt <span class="exp">例如:${(2 gt 1)?string('2 gt 1', '2 lte 1')}</span></dd>
<dd>大于等于:gte <span class="exp">例如:${(2 gte 2)?string('2 gte 2', '2 lt 2')}</span></dd>
<dd>小于(1):< <span
class="exp">例如:${(1 < 2)?string('1 < 2', '1 > 2')}。<strong>注:使用< 时必须加括号,否则可能会被当成普通的标签闭合符号而引起报错</strong></span>
</dd>
<dd>小于(2):lt <span class="exp">例如:${(1 lt 2)?string('1 lt 2', '1 gte 2')}</span></dd>
<dd>小于等于:lte <span class="exp">例如:${(2 lte 2)?string('2 lte 2', '2 gt 2')}</span></dd>
</dl>
<dl>
<dt>boolean</dt>
<dd>普通boolean输出:<span class="exp">${bol}</span></dd>
<dd>boolean判断输出:<span class="exp">${bol?string('true的时候显示','false的时候显示')}</span></dd>
</dl>
<dl>
<dt>日期</dt>
<dd>${dateObj?date}</dd>
<dd>${dateObj?time}</dd>
<dd>${dateObj?string("yyyy-MM-dd HH:mm:ss.SSS")}</dd>
</dl>
<dl>
<dt>import</dt>
<dd>
<#import "import.ftl" as importObj>
<p>${importObj.importStr}</p>
<p>${importObj.importStr1}</p>
</dd>
</dl>
<dl>
<dt>macro宏模板</dt>
<dd>
<#macro listMacro title items>
<p>${title?cap_first}:
<ul>
<#list items as item>
<li>${item?cap_first}</li>
</#list>
</ul>
<#nested >
</#macro>
</dd>
<dd>
<@listMacro items=["item1", "item2", "item3"] title="Items">
nested标签表示可以插入自定义的内容
</@listMacro>
</dd>
</dl>
include
<#include "eclipse.ftl">
</body>
</html>