更新记录

1.0.0(2026-01-09) 下载此版本

注意此文件直接就是源文件,可直接WEB_HT-ImagePreview将这个文件夹放入项目的components文件夹或任意位置开箱即可,项目index.vue文件中有使用按钮。开箱即用,支持vue3/js或vue3/ts。具体使用案例方式见.md。 ps:该插件主要解决需要在大图预览中展示描述信息需求。


平台兼容性

WEB_HT-ImagePreview 大图预览组件

uni-app TypeScript WeChat License

一个功能强大、高度可定制的大图预览组件,专为 uni-app + Vue3 + TypeScript 项目设计,完美适配微信小程序。

✨ 亮点特色

  • 🎨 高度可定制 - 支持自定义样式、颜色、插槽等
  • 🔧 TypeScript 支持 - 完整的类型定义,开发体验更佳
  • 📱 响应式设计 - 完美适配各种屏幕尺寸
  • 🚀 性能优化 - 懒加载、防抖、内存优化
  • 🎯 零依赖 - 基于 uni-app 原生组件,无第三方依赖
  • 💡 易于集成 - 简单的 API 设计,几行代码即可使用

🚀 快速开始

安装

  1. 将组件文件复制到你的项目中:

    src/components/WEB_HT-ImagePreview/
    ├── WEB_HT-ImagePreview.vue
    └── README.md
  2. 在页面中引入组件:

    <script setup lang="ts">
    import ImagePreview from '@/components/WEB_HT-ImagePreview/WEB_HT-ImagePreview.vue'
    </script>

30秒快速上手

<template>
  <view>
    <!-- 点击触发预览 -->
    <image 
      v-for="(item, index) in images" 
      :key="index"
      :src="item.url" 
      @click="showPreview(index)"
    />

    <!-- 大图预览 -->
    <ImagePreview
      :visible="visible"
      :image-list="images"
      :initial-index="currentIndex"
      close-button-position="left"
      @close="visible = false"
    />
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const images = ref([
  { url: 'https://example.com/image1.jpg' },
  { url: 'https://example.com/image2.jpg' }
])

const visible = ref(false)
const currentIndex = ref(0)

const showPreview = (index: number) => {
  currentIndex.value = index
  visible.value = true
}
</script>

🎆 功能特性

核心功能

  • 图片列表预览 - 支持多张图片轮播预览
  • 手势操作 - 滑动切换、点击关闭、背景关闭
  • 灵活定位 - 支持指定初始显示的图片索引
  • 智能适配 - 图片自适应屏幕尺寸,保持比例

UI 组件

  • 关闭按钮 - 可控制显示/隐藏,支持自定义图标,支持左右位置设置,智能适配安全区域
  • 计数器 - 显示当前图片位置(如:1/5)
  • 指示点 - 支持显示/隐藏,自定义颜色
  • 加载状态 - 图片加载中、错误状态处理

高级特性

  • 插槽系统 - 默认插槽、关闭按钮插槽
  • 事件系统 - 切换、关闭、加载等事件回调
  • 类型安全 - 完整的 TypeScript 类型定义
  • 数据灵活 - 支持多种图片字段名格式

📚 API 文档

Props 属性

属性名 类型 默认值 必填 说明
visible Boolean false 控制预览组件的显示/隐藏
imageList ImageItem[] [] 图片数据数组
initialIndex Number 0 初始显示的图片索引
showCloseButton Boolean true 是否显示关闭按钮
showCounter Boolean true 是否显示图片计数器
showIndicatorDots Boolean false 是否显示轮播指示点
indicatorColor String 'rgba(255,255,255,0.5)' 指示点颜色
indicatorActiveColor String '#ffffff' 激活状态指示点颜色
imageField String 'imgUrl' 图片字段名,支持 imgUrl/url/src

ImageItem 类型定义

interface ImageItem {
  imgUrl?: string    // 首选图片字段
  url?: string       // 备选图片字段
  src?: string       // 备选图片字段
  [key: string]: any // 其他自定义字段
}

Events 事件

事件名 触发时机 参数类型 说明
@close 点击关闭按钮或背景时 () 关闭预览时触发
@change 切换图片时 (index: number, image: ImageItem) 图片切换事件
@imageLoad 图片加载完成 (index: number) 图片加载成功回调
@imageError 图片加载失败 (index: number) 图片加载错误回调

Slots 插槽

默认插槽 (#default)

用于在预览界面上显示自定义内容,支持绝对定位。

插槽参数:

  • currentImage: ImageItem - 当前显示的图片对象
  • currentIndex: number - 当前图片索引(从0开始)
  • total: number - 图片总数
<template #default="{ currentImage, currentIndex, total }">
  <view class="custom-overlay">
    <text>{{ currentImage.title }}</text>
    <text>{{ currentIndex + 1 }}/{{ total }}</text>
  </view>
</template>

关闭按钮插槽 (#close-icon)

用于自定义关闭按钮的样式和内容。

<template #close-icon>
  <image src="/static/close-icon.png" class="custom-close" />
</template>

📝 使用示例

🎨 基础用法

最简单的图片预览实现:

<template>
  <view class="gallery">
    <!-- 图片网格 -->
    <view class="image-grid">
      <image 
        v-for="(item, index) in imageList" 
        :key="index"
        :src="item.imgUrl" 
        mode="aspectFill"
        class="grid-item"
        @click="showPreview(index)"
      />
    </view>

    <!-- 大图预览 -->
    <ImagePreview
      :visible="previewVisible"
      :image-list="imageList"
      :initial-index="currentIndex"
      @close="previewVisible = false"
      @change="onImageChange"
    />
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import ImagePreview from '@/components/WEB_HT-ImagePreview/WEB_HT-ImagePreview.vue'

interface ImageItem {
  imgUrl: string
  title?: string
  desc?: string
}

const imageList = ref<ImageItem[]>([
  {
    imgUrl: 'https://example.com/image1.jpg',
    title: '风景照片1',
    desc: '美丽的山水风光'
  },
  {
    imgUrl: 'https://example.com/image2.jpg',
    title: '风景照片2',
    desc: '壮丽的日出景色'
  }
])

const previewVisible = ref(false)
const currentIndex = ref(0)

const showPreview = (index: number) => {
  currentIndex.value = index
  previewVisible.value = true
}

const onImageChange = (index: number, image: ImageItem) => {
  console.log(`当前显示第${index + 1}张图片:`, image.title)
}
</script>

<style lang="scss">
.gallery {
  padding: 20rpx;
}

.image-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20rpx;

  .grid-item {
    width: 100%;
    height: 300rpx;
    border-radius: 10rpx;

    &:active {
      opacity: 0.8;
    }
  }
}
</style>

🔥 高级用法

带自定义插槽、操作按钮和样式定制:

<template>
  <view>
    <!-- 触发按钮 -->
    <button @click="showGallery">查看图片库 ({{ imageList.length }} 张)</button>

    <!-- 高级预览组件 -->
    <ImagePreview
      :visible="visible"
      :image-list="imageList"
      :initial-index="currentIndex"
      :show-indicator-dots="true"
      :show-counter="true"
      indicator-color="rgba(255, 255, 255, 0.6)"
      indicator-active-color="#007aff"
      @close="visible = false"
      @change="onImageChange"
      @imageLoad="onImageLoad"
      @imageError="onImageError"
    >
      <!-- 自定义内容区域 -->
      <template #default="{ currentImage, currentIndex, total }">
        <!-- 图片信息显示 -->
        <view class="image-info" v-if="currentImage.title">
          <view class="title">{{ currentImage.title }}</view>
          <view class="desc" v-if="currentImage.desc">{{ currentImage.desc }}</view>
          <view class="meta">
            <text>第 {{ currentIndex + 1 }} / {{ total }} 张</text>
            <text v-if="currentImage.size">· {{ formatFileSize(currentImage.size) }}</text>
          </view>
        </view>

        <!-- 操作按钮组 -->
        <view class="action-buttons">
          <button 
            class="action-btn" 
            @click="downloadImage(currentImage)"
          >
            📥 下载
          </button>
          <button 
            class="action-btn" 
            @click="shareImage(currentImage)"
          >
            📤 分享
          </button>
          <button 
            class="action-btn" 
            @click="collectImage(currentImage)"
            :class="{ collected: currentImage.collected }"
          >
            {{ currentImage.collected ? '♥' : '♡' }} 收藏
          </button>
        </view>
      </template>

      <!-- 自定义关闭按钮 -->
      <template #close-icon>
        <image src="/static/icons/close.png" class="custom-close-icon" />
      </template>
    </ImagePreview>
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import ImagePreview from '@/components/WEB_HT-ImagePreview/WEB_HT-ImagePreview.vue'

interface ExtendedImageItem {
  imgUrl: string
  title: string
  desc?: string
  size?: number
  collected?: boolean
}

const imageList = ref<ExtendedImageItem[]>([
  {
    imgUrl: 'https://example.com/image1.jpg',
    title: '壮丽的山脉',
    desc: '拍摄于某某国家公园,日出时分的金光洒在山峰上',
    size: 2048000,
    collected: false
  },
  {
    imgUrl: 'https://example.com/image2.jpg',
    title: '宁静的湖泊',
    desc: '清晨的雾气轻轻浮在湖面上,如梦如幻',
    size: 1756000,
    collected: true
  }
])

const visible = ref(false)
const currentIndex = ref(0)

const showGallery = () => {
  currentIndex.value = 0
  visible.value = true
}

const onImageChange = (index: number, image: ExtendedImageItem) => {
  console.log('切换到:', image.title)
  // 可以在这里上报浏览统计
}

const onImageLoad = (index: number) => {
  console.log(`第 ${index + 1} 张图片加载完成`)
}

const onImageError = (index: number) => {
  console.error(`第 ${index + 1} 张图片加载失败`)
  uni.showToast({ title: '图片加载失败', icon: 'none' })
}

const downloadImage = async (image: ExtendedImageItem) => {
  try {
    const res = await uni.downloadFile({ url: image.imgUrl })
    await uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath })
    uni.showToast({ title: '保存成功' })
  } catch (error) {
    uni.showToast({ title: '保存失败', icon: 'none' })
  }
}

const shareImage = (image: ExtendedImageItem) => {
  uni.share({
    provider: 'weixin',
    scene: 'WXSceneSession',
    type: 2,
    imageUrl: image.imgUrl,
    title: image.title
  })
}

const collectImage = (image: ExtendedImageItem) => {
  image.collected = !image.collected
  uni.showToast({ 
    title: image.collected ? '已收藏' : '取消收藏',
    icon: 'none'
  })
}

const formatFileSize = (bytes: number): string => {
  if (bytes < 1024) return bytes + 'B'
  if (bytes < 1048576) return Math.round(bytes / 1024) + 'KB'
  return Math.round(bytes / 1048576 * 100) / 100 + 'MB'
}
</script>

<style lang="scss">
.image-info {
  position: absolute;
  bottom: 80rpx;
  left: 30rpx;
  right: 30rpx;
  background: linear-gradient(to top, rgba(0,0,0,0.8), rgba(0,0,0,0.4));
  backdrop-filter: blur(10rpx);
  color: white;
  padding: 30rpx;
  border-radius: 20rpx;

  .title {
    font-size: 32rpx;
    font-weight: bold;
    margin-bottom: 10rpx;
  }

  .desc {
    font-size: 26rpx;
    opacity: 0.9;
    line-height: 1.4;
    margin-bottom: 15rpx;
  }

  .meta {
    font-size: 22rpx;
    opacity: 0.7;
  }
}

.action-buttons {
  position: absolute;
  top: 100rpx;
  left: 30rpx;
  display: flex;
  gap: 20rpx;

  .action-btn {
    background: rgba(0, 0, 0, 0.6);
    color: white;
    border: none;
    padding: 15rpx 25rpx;
    border-radius: 25rpx;
    font-size: 24rpx;
    backdrop-filter: blur(10rpx);

    &:active {
      background: rgba(0, 0, 0, 0.8);
    }

    &.collected {
      color: #ff6b6b;
    }
  }
}

.custom-close-icon {
  width: 40rpx;
  height: 40rpx;
}
</style>

支持不同图片字段名

<template>
  <ImagePreview
    :visible="previewVisible"
    :image-list="imageList"
    image-field="src"
    @close="hidePreview"
  />
</template>

<script setup>
// 图片数据使用不同的字段名
const imageList = ref([
  { src: 'https://example.com/image1.jpg' },
  { src: 'https://example.com/image2.jpg' }
])
</script>

💡 最佳实践

性能优化

// 1. 图片懒加载策略
const loadImageOnDemand = (index: number) => {
  const preloadRange = 2 // 预加载前后2张
  const start = Math.max(0, index - preloadRange)
  const end = Math.min(imageList.value.length - 1, index + preloadRange)

  // 只加载可视范围内的图片
  return imageList.value.slice(start, end + 1)
}

// 2. 图片压缩和缩略图
interface OptimizedImageItem {
  imgUrl: string        // 原图
  thumbnailUrl: string  // 缩略图
  title: string
}

// 3. 内存释放
const clearImageCache = () => {
  // 在适当时机清理图片缓存
  plus?.cache?.clear()
}

响应式设计

// 适配不同屏幕尺寸
.custom-overlay {
  // 小屏幕设备
  @media (max-width: 750rpx) {
    font-size: 24rpx;
    padding: 20rpx;
  }

  // 大屏幕设备
  @media (min-width: 751rpx) {
    font-size: 28rpx;
    padding: 30rpx;
  }
}

// 安全区域适配
.action-buttons {
  top: calc(100rpx + env(safe-area-inset-top));
  padding-top: env(safe-area-inset-top);
}

错误处理

// 完整的错误处理机制
const handleImageError = (index: number) => {
  console.error(`图片加载失败: ${imageList.value[index]?.imgUrl}`)

  // 显示默认图片
  if (imageList.value[index]) {
    imageList.value[index].imgUrl = '/static/images/placeholder.jpg'
  }

  // 用户友好提示
  uni.showToast({
    title: '图片加载失败,显示默认图片',
    icon: 'none',
    duration: 2000
  })
}

// 网络检测
const checkNetworkStatus = () => {
  uni.getNetworkType({
    success: (res) => {
      if (res.networkType === 'none') {
        uni.showToast({
          title: '网络连接失败,请检查网络',
          icon: 'none'
        })
      }
    }
  })
}

❓ 常见问题

Q: 图片显示不出来怎么办?

A: 检查以下几点:

  1. 图片路径是否正确

    // 检查图片 URL 是否可访问
    console.log('Image URL:', imageItem.imgUrl)
  2. 网络权限配置

    // manifest.json 中配置
    "mp-weixin": {
     "setting": {
       "urlCheck": false
     }
    }
  3. 域名白名单:微信小程序需要在后台配置图片域名白名单。

Q: 滑动切换不流畅怎么办?

A: 优化建议:

// 增加 GPU 加速
.image-swiper {
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
}

// 减少复杂动画
.preview-image {
  will-change: transform;
}

Q: 如何自定义加载动画?

A: 使用插槽添加加载指示器:

<template #default="{ currentImage }">
  <view class="loading" v-if="!imageLoaded">
    <view class="loading-spinner"></view>
    <text>加载中...</text>
  </view>
</template>

Q: 如何实现图片缩放功能?

A: 组件主要专注于预览,缩放功能建议使用专门的图片缩放组件或自行实现手势交互。

🛠️ 兼容性

平台 支持状态 注意事项
微信小程序 ✅ 完全支持 需配置图片域名白名单
H5 ✅ 完全支持 注意跨域问题
App ✅ 完全支持 性能较优
支付宝小程序 ⚠️ 需测试 部分样式可能需调整
百度小程序 ⚠️ 需测试 部分样式可能需调整

版本要求

  • uni-app: >= 3.0.0
  • Vue: >= 3.0.0
  • TypeScript: >= 4.0.0 (可选)

⭐ 感谢

如果这个组件对你有帮助,请考虑给个 Star ⭐,这对我们来说意义重大!

同时也欢迎:

  • 👍 点赞和评论
  • 📢 分享给其他开发者
  • 💬 提供使用反馈
  • 🐛 报告 Bug 和建议

📄 许可证

本项目采用 MIT License 许可证 - 查看 LICENSE 文件获取详细信息。

这意味着你可以:

  • ✅ 商业使用
  • ✅ 修改代码
  • ✅ 分发
  • ✅ 私人使用
  • ✅ 专利使用

但是需要

  • 保留原作者版权声明
  • 保留许可证声明

✨ 由 [webHt] 精心制作 ✨
如果这个组件对你有帮助,请考虑给个 Star ⭐

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。