viewerjs+vue3 using typescript
安装包
npm install v-viewer viewerjs npm i fontawesome-4.7 npm install @fortawesome/fontawesome-svg-core npm install @fortawesome/free-solid-svg-icons npm install @fortawesome/vue-fontawesome@prerelease npm install @fortawesome/free-regular-svg-icons npm install @fortawesome/free-brands-svg-icons npm install viewerjs @types/viewerjs --save
main.ts
/*
* @creater: geovindu
* @since: 2025-06-24 20:03:42
* @LastAuthor: geovindu
* @lastTime: 2025-10-31 22:06:13
* @文件相对于项目的路径: \jsstudy\markmapdemo\src\main.ts
* @message: geovindu
* @IDE: vscode
* @Development: node.js 20, vuejs3.0
* @package:
* @ISO: windows10
* @database: mysql 8.0 sql server 2019 postgresSQL 16
* Copyright (c) 2025 by geovindu email:geovindu@163.com, All Rights Reserved.
*/
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import Viewer from 'viewerjs';
import "fontawesome-4.7/css/font-awesome.css";
import { library } from '@fortawesome/fontawesome-svg-core';
import { faUserSecret } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import 'viewerjs/dist/viewer.css';
Viewer.setDefaults({
navbar: true,
title: true,
toolbar: {
prev: true,
next: true,
},
});
const app = createApp(App)
.component('font-awesome-icon', FontAwesomeIcon)
app.use(createPinia())
app.use(router)
app.use(() => Viewer);
app.mount('#app')
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
<!-- 图片容器 -->
<div ref="viewerContainer" style="margin: 20px;">
<img
v-for="src in images"
:key="src"
:src="getThumbnail(src)"
:data-src="src"
:alt="extractFilename(src)"
class="viewer-image"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import Viewer from 'viewerjs';
import 'viewerjs/dist/viewer.css';
// 🔹 定义类型
type ImageSrc = string;
// 🔹 图片列表(类型化)
const images = ref<ImageSrc[]>([
'1.png',
'2.jpg',
'3.jpg',
]);
// 🔹 提取文件名(带类型)
const extractFilename = (url: string): string => {
try {
const pathname = new URL(url, location.origin).pathname;
const filename = pathname.split('/').pop() || 'image';
return filename.split('?')[0].split('#')[0]; // 去除查询参数
} catch (e) {
const match = url.match(/[^/\\?#]+(?=[^/\\]*$)/);
return match ? match[0] : 'image';
}
};
// 🔹 缩略图生成函数
const getThumbnail = (url: string): string => {
if (url.includes('picsum.photos')) {
return url.replace(/(\d+)\/(\d+)/, '200/150');
}
return url;
};
// 🔹 Viewer 容器引用
const viewerContainer = ref<HTMLDivElement | null>(null);
let viewer: Viewer | null = null;
// 🔹 下载处理函数
const handleDownload = (): void => {
if (!viewer || !viewer.image) return;
const currentImage = viewer.image;
const filename = extractFilename(currentImage.src);
const tempImage = new Image();
tempImage.crossOrigin = 'Anonymous';
tempImage.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = tempImage.width;
canvas.height = tempImage.height;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.drawImage(tempImage, 0, 0);
const ext = filename.split('.').pop()?.toLowerCase() || 'jpeg';
const mimeTypeMap: Record<string, string> = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
webp: 'image/webp',
bmp: 'image/bmp',
};
const mimeType = mimeTypeMap[ext] || 'image/jpeg';
const dataUrl = canvas.toDataURL(mimeType, 0.95);
const a = document.createElement('a');
a.href = dataUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
tempImage.onerror = () => {
const a = document.createElement('a');
a.href = currentImage.src;
a.download = filename;
a.target = '_blank';
a.rel = 'noopener noreferrer';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
tempImage.src = currentImage.src;
};
// 🔹 组件生命周期
onMounted(() => {
if (viewerContainer.value) {
viewer = new Viewer(viewerContainer.value, {
url: 'data-src',
toolbar: {
zoomIn: true,
zoomOut: true,
oneToOne: true,
reset: true,
prev: true,
next: true,
download: {
show: true,
//size: 'large',
click() {
handleDownload();
},
},
},
navbar: true,
title: (image: HTMLImageElement) => extractFilename(image.src),
viewed() {
console.log('查看:', extractFilename(viewer?.image.src || ''));
},
});
}
});
onUnmounted(() => {
if (viewer) {
viewer.destroy();
viewer = null;
}
});
</script>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>
输出:

哲学管理(学)人生, 文学艺术生活, 自动(计算机学)物理(学)工作, 生物(学)化学逆境, 历史(学)测绘(学)时间, 经济(学)数学金钱(理财), 心理(学)医学情绪, 诗词美容情感, 美学建筑(学)家园, 解构建构(分析)整合学习, 智商情商(IQ、EQ)运筹(学)生存.---Geovin Du(涂聚文)
浙公网安备 33010602011771号