download 属性定义了下载链接的地址。该属性需要下载资源是同源的。
// 同源 此时是预览图片
<a target="_blank" href="/test.jpg">预览</a>
// 同源 此时是下载图片
<a target="_blank" href="/test.jpg" download>下载</a>
在 vue 中,通过代理后也可以实现
// href="http://127.0.0.1:3000/uploads/20230731/4dbbe8b13342145d34e99ce00.webp.jpg"
// 此时用 /api 代替 http://127.0.0.1:3000 , 如下
<a href="/api/uploads/20230731/4dbbe8b13342145d34e99ce00.webp.jpg">预览</a>
<a href="/api/uploads/20230731/4dbbe8b13342145d34e99ce00.webp.jpg" download="">下载</a>
浏览器默认都是预览
// 不同源 download 失效, 此时都是预览图片,而不是下载
<a href="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg">预览</a>
<a href="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg" download>预览</a>
不同源通过 href = src + '?response-content-type=application/octet-stream';
实现强制下载
// 全是下载
<a href="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg?response-content-type=application/octet-stream">下载</a>
<a href="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg?response-content-type=application/octet-stream" download>下载</a>
window.open('https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg');
...
规则和上面一样
写法一
function downloadFile(src: string, fileName?: string) {
const a = document.createElement('a');
a.download = fileName ?? String(new Date().getTime());
a.target = '_blank';
a.href = src;
a.click();
}
写法二
function downloadFile(src: string, fileName?: string) {
const a = document.createElement('a');
// 创建一个单击事件
const event = new MouseEvent('click');
// 设置文件名
a.download = fileName ?? String(new Date().getTime());
// 将生成的URL设置为a.href属性
a.href = src;
// 触发a的单击事件
a.dispatchEvent(event);
}
function handleDownload(){
// 下载图片地址和图片名
const image = new Image();
// 解决跨域 Canvas 污染问题
image.setAttribute('crossOrigin', 'anonymous');
image.onload = function () {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const context = canvas.getContext('2d');
context?.drawImage(image, 0, 0, image.width, image.height);
const url = canvas.toDataURL('image/png'); // 得到图片的base64编码数据
downloadFile(url);
};
image.src = 'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg';
}
function downloadFile(fileUrl,fileName){
axios.get(fileUrl, { responseType: "blob" }).then(response => {
const blob = new Blob([response.data]);
const a = document.createElement("a");
// 创建下载的链接
a.href = window.URL.createObjectURL(blob);
a.download = fileName;
a.style.display = "none";
//a标签追加元素到body内
document.body.appendChild(a);
//模拟点击下载
a.click();
// 下载完成移除元素
document.body.removeChild(a);
// 释放掉blob对象
window.URL.revokeObjectURL(a.href);
}).catch(console.error);
}
返回的格式
function handleDownload() {
axios
.get("http://127.0.0.1:3001/api/downloads", {
responseType: "blob",
})
.then((res) => {
console.log("res", res);
downloadFile(URL.createObjectURL(res.data));
});
}
// 也可以直接请求图片地址
function handleDownload2() {
axios
.get("http://127.0.0.1:3001/uploads/20230727/baca7d568e3cbbd1a0c360700.webp.jpg", {
responseType: "blob",
})
.then((res) => {
console.log("res", res);
const blob = new Blob([res.data]);
downloadFile(URL.createObjectURL(res.data));
});
}
所有情况通用的方式: 后端设置下载请求的响应头 Content-Disposition: attachment; filename=filename.jpg
后端 express 为例。
const express = require("express");
const cors = require("cors");
const app = express();
app.use(cors());
app.get("/downloads", (req, res) => {
res.setHeader("Content-Disposition", "attachment;filename=xxx.jpg");
res.sendFile(__dirname + "/test.jpg");
});
app.listen(3000, () => {
console.log("http://127.0.0.1:3000");
});
koa 为例
const send = require("koa-send");
const path = require("node:path");
router.get("/downloads", async (ctx, next) => {
const filePath = "/uploads/20230727/baca7d568e3cbbd1a0c360700.webp.jpg";
// ctx.attachment(filePath);
// 响应文件格式
await send(ctx, filePath, { root: path.join(__dirname, "../public") });
});
前端
// 此时由于后端控制也是强制下载
<a href="http://127.0.0.1:3001/api/downloads">下载</a>
vue-office
效果
#docx文档预览组件
npm install @vue-office/docx vue-demi@0.13.11
#excel文档预览组件
npm install @vue-office/excel vue-demi@0.13.11
#pdf文档预览组件
npm install @vue-office/pdf vue-demi@0.13.11
<script lang="ts" setup>
import { ref } from "vue";
//引入VueOfficeDocx组件
import VueOfficeDocx from "@vue-office/docx";
//引入相关样式
import "@vue-office/docx/lib/index.css";
//引入VueOfficeExcel组件
import VueOfficeExcel from "@vue-office/excel";
//引入相关样式
import "@vue-office/excel/lib/index.css";
//引入VueOfficePdf组件
import VueOfficePdf from "@vue-office/pdf";
const docx = ref("");
const excel = ref("");
const pdf = ref("");
function rendered() {
console.log("渲染完成");
}
function errorHandler() {
console.log("渲染失败");
}
</script>
<template>
<div>
<button @click="docx = 'http://127.0.0.1:3000/uploads/20231213/c938a52037e05a351d4aaf300.docx'">预览 docx</button>
<vue-office-docx v-if="docx" :src="docx" class="preview" style="height: 100vh" @rendered="rendered" />
</div>
<div>
<button @click="excel = 'http://127.0.0.1:3000/uploads/20231213/c938a52037e05a351d4aaf301.xlsx'">
预览 excel
</button>
<vue-office-excel v-if="excel" :src="excel" class="preview" style="height: 100vh" @rendered="rendered" />
</div>
<div>
<button @click="pdf = 'http://static.shanhuxueyuan.com/test.pdf'">预览 pdf</button>
<vue-office-pdf
v-if="pdf"
:src="pdf"
class="preview"
style="height: 100vh"
@rendered="rendered"
@error="errorHandler"
/>
</div>
</template>