html!!!save

This commit is contained in:
chenhao 2026-05-26 21:25:23 +08:00
parent 4f22196d68
commit 107ebd7a1d
3 changed files with 96 additions and 80 deletions

View File

@ -1,74 +1,107 @@
/** /**
* HTML转图片工具 * HTML转图片工具
* 基于 html2canvas 实现兼容低版本 Safari/微信 * 基于 html-to-image 将DOM元素转换为图片
*/ */
import html2canvas from 'html2canvas'; import { toCanvas } from 'html-to-image'
/** /**
* 将DOM元素转换为图片 * 将DOM元素转换为图片
* @param {string|HTMLElement} selector - DOM选择器或元素 * @param {string|HTMLElement} selector - DOM选择器或元素
* @param {Object} options - 配置选项 * @param {Object} options - 配置选项
* @param {number} [options.width] - 自定义宽度会应用到元素上
* @param {number} [options.height] - 自定义高度会应用到元素上
* @param {number} [options.canvasWidth] - 输出画布宽度
* @param {number} [options.canvasHeight] - 输出画布高度
* @param {number} [options.pixelRatio] - 像素比例默认2 * @param {number} [options.pixelRatio] - 像素比例默认2
* @param {boolean} [options.useCORS] - 是否允许跨域默认true
* @param {string} [options.format] - 图片格式'png''jpeg'默认'png' * @param {string} [options.format] - 图片格式'png''jpeg'默认'png'
* @param {number} [options.quality] - 图片质量0-1仅jpeg有效默认1.0 * @param {number} [options.quality] - 图片质量0-1仅jpeg有效默认1.0
* @returns {Promise<string>} - 返回base64图片数据URL * @returns {Promise<string>} - 返回base64图片数据URL
*/ */
export async function toDataURL(selector, options = {}) { export async function toDataURL(selector, options = {}) {
const { const {
pixelRatio = 2, width,
format = 'png', height,
quality = 1.0 canvasWidth,
} = options; canvasHeight,
pixelRatio = 2,
useCORS = true,
format = 'png',
quality = 1.0
} = options
const element = typeof selector === 'string' ? document.querySelector(selector) : selector; const element = typeof selector === 'string' ? document.querySelector(selector) : selector
if (!element) throw new Error('Element not found'); if (!element) {
throw new Error('Element not found')
}
const canvas = await html2canvas(element, { const canvasEl = await toCanvas(element, {
scale: pixelRatio, width,
useCORS: true, height,
allowTaint: false, canvasWidth,
backgroundColor: '#ffffff', canvasHeight,
dpi: window.devicePixelRatio * pixelRatio, pixelRatio,
logging: false cors: useCORS,
}); backgroundColor: '#ffffff'
})
const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png'; const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png'
return canvas.toDataURL(mimeType, quality); return canvasEl.toDataURL(mimeType, quality)
} }
/** /**
* 将DOM元素转换为图片并下载 * 将DOM元素转换为图片并下载
* @param {string|HTMLElement} selector - DOM选择器或元素
* @param {string} [filename] - 下载文件名不含扩展名
* @param {Object} options - 配置选项同toDataURL
*/ */
export async function toImageDownload(selector, filename = 'download', options = {}) { export async function toImageDownload(selector, filename = 'download', options = {}) {
const dataUrl = await toDataURL(selector, options); const dataUrl = await toDataURL(selector, options)
const link = document.createElement('a');
link.download = filename + '.png'; const link = document.createElement('a')
link.href = dataUrl; link.download = filename + '.png'
document.body.appendChild(link); link.href = dataUrl
link.click(); document.body.appendChild(link)
document.body.removeChild(link); link.click()
document.body.removeChild(link)
} }
/** /**
* 转为 Blob * 将DOM元素转换为Blob
* @param {string|HTMLElement} selector - DOM选择器或元素
* @param {Object} options - 配置选项同toDataURL
* @returns {Promise<Blob>}
*/ */
export function toBlob(selector, options = {}) { export async function toBlob(selector, options = {}) {
return new Promise(async (resolve, reject) => { const dataUrl = await toDataURL(selector, options)
try {
const dataUrl = await toDataURL(selector, options); const arr = dataUrl.split(',')
const arr = dataUrl.split(','); const mime = arr[0].match(/:(.*?);/)[1]
const mime = arr[0].match(/:(.*?);/)[1]; const bstr = atob(arr[1])
const bstr = atob(arr[1]); let n = bstr.length
const u8arr = new Uint8Array(bstr.length); const u8arr = new Uint8Array(n)
for (let i = 0; i < bstr.length; i++) { while (n--) {
u8arr[i] = bstr.charCodeAt(i); u8arr[n] = bstr.charCodeAt(n)
} }
resolve(new Blob([u8arr], { type: mime })); return new Blob([u8arr], { type: mime })
} catch (e) {
reject(e);
}
});
} }
export default { toDataURL, toImageDownload, toBlob }; /**
* 通过 dataURL 直接下载图片
*/
export function downloadByDataURL(dataUrl, filename = 'download') {
const link = document.createElement('a')
link.download = filename + '.png'
link.href = dataUrl
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
export default {
toDataURL,
toImageDownload,
toBlob,
downloadByDataURL
}

View File

@ -11,7 +11,7 @@
<img src="/img/paycode.jpg" alt="" style="width: 100%; display: block;" class="bg-img" @load="onBgLoad"> <img src="/img/paycode.jpg" alt="" style="width: 100%; display: block;" class="bg-img" @load="onBgLoad">
<div class="user-info"> <div class="user-info">
<div class="info"> <div class="info">
<img :src="$file(shopimg)" alt="" style="border-radius: 50%;"> <img :src="$file(shopimg)" alt="">
</div> </div>
<div class="name"> <div class="name">
<span>{{ shopname?.length > 8 ? shopname.slice(0, 8) + '...' : shopname }}</span> <span>{{ shopname?.length > 8 ? shopname.slice(0, 8) + '...' : shopname }}</span>
@ -33,7 +33,7 @@
</template> </template>
<script> <script>
import { toDataURL } from '@/utils/html2image' import { toDataURL, downloadByDataURL } from '@/utils/html2image'
export default { export default {
name: 'PayCode', name: 'PayCode',
@ -94,38 +94,21 @@ export default {
async handleSave() { async handleSave() {
if (this.loading) return if (this.loading) return
// try {
if (this.generatedImage) { this.loading = true
this.downloadByBlob(this.generatedImage, `收款码_${this.shopname}.png`) if (!this.generatedImage) {
this.$showSuccessToast('保存成功') await this.generateImage()
return }
if (this.generatedImage) {
downloadByDataURL(this.generatedImage, `收款码_${this.shopname}`)
this.$showSuccessToast('保存成功')
}
} catch (e) {
console.error('保存失败:', e)
this.$showFailToast('保存失败')
} finally {
this.loading = false
} }
//
await this.generateImage()
if (this.generatedImage) {
this.downloadByBlob(this.generatedImage, `收款码_${this.shopname}.png`)
this.$showSuccessToast('保存成功')
}
},
downloadByBlob(dataUrl, filename) {
const arr = dataUrl.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
const blob = new Blob([u8arr], { type: mime })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.download = filename
link.href = url
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
}, },
}, },
} }
@ -171,7 +154,7 @@ export default {
img { img {
width: 6.4vw; width: 6.4vw;
height: 6.4vw; height: 6.4vw;
border-radius: 50%; // border-radius: 50%;
} }
} }

View File

@ -167,7 +167,7 @@ export default {
color: #3a0a05; color: #3a0a05;
position: absolute; position: absolute;
left: 20.27vw; left: 20.27vw;
bottom: 34vw; bottom: 32.4vw;
} }
.icon { .icon {
@ -175,7 +175,7 @@ export default {
width: 10.67vw; width: 10.67vw;
height: 10.67vw; height: 10.67vw;
left: 20.27vw; left: 20.27vw;
bottom: 42vw; bottom: 43vw;
border-radius: 50%; border-radius: 50%;
} }