当前位置: 首页 > article >正文

DeepSeek 助力 Vue 开发:打造丝滑的缩略图列表(Thumbnail List)

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕

共同探索软件研发!敬请关注【宝码香车】
关注描述

csdngif标识

目录

  • DeepSeek 助力 Vue 开发:打造丝滑的缩略图列表(Thumbnail List)
    • 📚前言
    • 📚页面效果
    • 📚指令输入
      • 属性定义
        • 数据相关属性
        • 布局相关属性
        • 样式相关属性
        • 交互相关属性
      • 事件
      • 其他
    • 📚think
      • 📘组件代码
    • 📚代码测试
    • 📚填写参数后主要代码
      • 📘定义组件 src\views\ThumbnailListView.vue
    • 📚测试代码正常跑通,附其他基本代码
      • 📘编写路由 src\router\index.js
      • 📘编写展示入口 src\App.vue
    • 📚页面效果
    • 📚相关文章


📚📗📕📘📖🕮💡📝🗂️✍️🛠️💻🚀🎉🏗️🌐🖼️🔗📊👉🔖⚠️🌟🔐⬇️·正文开始⬇️·🎥😊🎓📩😺🌈🤝🤖📜📋🔍✅🧰❓📄📢📈 🙋0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣🔟🆗*️⃣#️⃣

DeepSeek 助力 Vue 开发:打造丝滑的缩略图列表(Thumbnail List)

📚前言

在创意写作领域,一位小说家也分享了使用 DeepSeek 的奇妙体验。他在创作一部科幻小说时,遇到了创作瓶颈,故事的情节发展陷入了僵局。于是,他向 DeepSeek 寻求帮助,输入了小说的背景设定、人物特点和已有的情节框架。DeepSeek 迅速给出了多个富有创意的情节走向和故事转折建议,这些建议不仅符合小说的整体风格,还为他打开了新的创作思路。他根据 DeepSeek 的建议,对小说进行了重新构思,最终创作出了一部广受好评的科幻小说。他表示:“DeepSeek 的创意激发能力让我惊叹,它就像是一个创意宝库,总能在我最需要的时候提供新奇的想法。”

📚页面效果

页面效果

📚指令输入

已经创建好了一个基于Vue3的组合式API的项目(Composition API),并能正常运行起来,请帮我用 Vue3的组合式API(Composition API) 生成一个 缩略图列表(Thumbnail List) 的功能组件,所有代码都保存在components/ThumbnailList 下的文件夹中。功能组件的script标签中只有setup属性,使用普通 JavaScript 实现,不使用TypeScript。
功能要有,如下属性:

属性定义

数据相关属性
  1. items

    • 类型:数组
    • 描述:用于存储缩略图列表的数据,数组中的每个元素代表一个缩略图项,可以包含图片的 src、标题、描述等信息。
    • 示例[{ src: 'image1.jpg', title: 'Image 1' }, { src: 'image2.jpg', title: 'Image 2' }]
  2. itemKey

    • 类型:字符串
    • 描述:指定 items 数组中每个元素的唯一标识字段,用于 Vue 的 :key 绑定,确保列表渲染的高效性和正确性。
    • 示例:如果 items 数组中的每个元素有一个 id 字段作为唯一标识,则 itemKey 可以设置为 'id'
布局相关属性
  1. columns

    • 类型:数字
    • 描述:指定缩略图列表的列数,用于控制布局。
    • 示例3 表示列表将显示为 3 列。
  2. gutter

    • 类型:数字或字符串
    • 描述:缩略图项之间的间距,可以是像素值(如 20)或带有单位的值(如 '20px')。
样式相关属性
  1. thumbnailWidth

    • 类型:数字或字符串
    • 描述:指定缩略图的宽度,可以是像素值(如 100)或带有单位的值(如 '100px')。
  2. thumbnailHeight

    • 类型:数字或字符串
    • 描述:指定缩略图的高度,可以是像素值(如 100)或带有单位的值(如 '100px')。
  3. loadingPlaceholder

    • 类型:字符串
    • 描述:图片加载过程中显示的占位图的 src 地址。
交互相关属性
  1. selectedIndex

    • 类型:数字
    • 描述:指定当前选中的缩略图项的索引,用于实现选中状态的高亮显示。
  2. disabledIndexes

    • 类型:数组
    • 描述:指定禁用的缩略图项的索引数组,禁用的项不可点击。
    • 示例[1, 3] 表示索引为 1 和 3 的缩略图项被禁用。

事件

  1. click

    • 参数:当前点击的缩略图项的数据和索引
    • 描述:当用户点击缩略图项时触发,可用于实现点击缩略图的相关操作,如显示大图、跳转到详情页等。
  2. load

    • 参数:当前加载成功的缩略图项的数据和索引
    • 描述:当缩略图图片加载成功时触发,可用于实现加载成功后的特效或统计加载成功的图片数量。
  3. error

    • 参数:当前加载失败的缩略图项的数据和索引
    • 描述:当缩略图图片加载失败时触发,可用于显示加载失败的提示信息或替换为默认图片。
  4. select

    • 参数:当前选中的缩略图项的数据和索引
    • 描述:当缩略图项被选中时触发,可用于更新 selectedIndex 属性或执行其他选中相关的操作。

其他

  1. 响应式设计:考虑使用 CSS 媒体查询或 Vue 的响应式布局方案,使缩略图列表在不同屏幕尺寸下都能有良好的显示效果。
  2. 懒加载:对于大量的缩略图列表,使用懒加载技术可以提高页面的加载性能,只在缩略图进入可视区域时才加载图片。
  3. 键盘导航:支持键盘操作,如使用方向键选择缩略图项,回车键触发点击事件,提高组件的可访问性。
  4. 动画效果:为缩略图的选中、加载等状态添加适当的动画效果,提升用户体验。
  5. 自定义插槽:提供自定义插槽,允许用户自定义缩略图的内容,如在缩略图上添加自定义的徽章、标签等。

你有更好的建议也可以添加,要注明。组件定义好后给出5个及以上的调用示例。
下面是现有目录
vueAndDeepseek/
├── src/ # 源代码目录
│ ├── assets/ # 静态资源
│ │ ├── base.css
│ │ ├── main.css
│ │ └── logo.svg
│ ├── components/ # 组件目录
│ │ ├── HelloWorld.vue
│ │ ├── TheWelcome.vue
│ │ ├── WelcomeItem.vue
│ │ ├── Progress/
│ │ │ └── Progress.vue
│ │ ├── Accordion/
│ │ ├── BackToTop/
│ │ ├── Card/
│ │ ├── InfiniteScroll/
│ │ ├── Notification/
│ │ ├── Timeline/
│ │ ├── Switch/
│ │ ├── Tabs/
│ │ ├── Sidebar/
│ │ ├── Breadcrumbs/
│ │ ├── MasonryLayout/
│ │ ├── Rating/
│ │ ├── ColorPicker/
│ │ ├── RightClickMenu/
│ │ ├── RangePicker/
│ │ ├── Navbar/
│ │ ├── FormValidation/
│ │ ├── CopyToClipboard/
│ │ ├── ClickAnimations/
│ │ ├── ThumbnailList/
│ │ ├── DatePicker/
│ │ └── icons/
│ ├── router/ # 路由配置
│ │ └── index.js
│ ├── stores/ # Pinia 状态管理
│ │ └── counter.js
│ ├── views/ # 页面组件
│ │ ├── ProgressView.vue
│ │ ├── TabsView.vue
│ │ ├── SwitchView.vue
│ │ ├── TimelineView.vue
│ │ ├── NotificationView.vue
│ │ ├── CardView.vue
│ │ ├── InfiniteScrollView.vue
│ │ ├── BackToTopView.vue
│ │ ├── AccordionView.vue
│ │ ├── SidebarView.vue
│ │ ├── BreadcrumbsView.vue
│ │ ├── MasonryLayoutView.vue
│ │ ├── RatingView.vue
│ │ ├── ColorPickerView.vue
│ │ ├── RightClickMenuView.vue
│ │ ├── RangePickerView.vue
│ │ ├── NavbarView.vue
│ │ ├── FormValidationView.vue
│ │ ├── CopyToClipboardView.vue
│ │ ├── ClickAnimationsView.vue
│ │ ├── ThumbnailListView.vue
│ │ ├── DatePickerView.vue
│ │ └── AboutView.vue
│ ├── App.vue # 根组件
│ └── main.js # 应用入口
├── public/ # 公共资源目录
├── index.html # HTML 模板
├── package.json # 项目配置
├── vite.config.js # Vite 配置
└── node_modules/ # 依赖包目录

📚think

好的,我现在需要帮用户创建一个基于Vue3组合式API的缩略图列表组件。用户已经有一个项目,并且希望所有代码都保存在components/ThumbnailList文件夹下。首先,我需要明确组件的属性、事件以及其他要求,然后按照这些要求逐步构建组件。

首先,用户提到的属性分为数据相关、布局相关、样式相关和交互相关。数据方面,items数组和itemKey是必须的。布局方面需要处理columns和gutter,可能需要使用CSS Grid或者Flex布局来实现多列。样式方面,缩略图的宽高、占位图等需要处理。交互方面,选中的索引、禁用索引以及各种事件,比如点击、加载、错误、选择事件。

接下来,我需要考虑组件的结构。可能使用

  • 来构建列表,每个项包含图片、标题和描述。图片加载时可能需要懒加载,这里可以用Intersection Observer实现。同时,处理图片加载成功和失败的事件,触发对应的事件。

响应式设计方面,可能需要根据屏幕大小调整列数,或者使用媒体查询。但用户提到使用Vue的响应式布局方案,可能需要在组件内部监听窗口大小变化,动态调整columns属性?或者让用户通过props传递不同的columns,然后自己处理媒体查询?可能需要进一步明确。

懒加载的实现,当图片进入视口时加载,可以使用vueuse库中的useIntersectionObserver,或者自己实现。但用户希望使用普通JavaScript,可能需要自己写Intersection Observer的逻辑。

键盘导航方面,需要处理方向键和回车键的事件,这可能需要为每个缩略图项添加tabindex,并监听keydown事件。选中项的高亮显示可以通过CSS类来处理。

自定义插槽部分,用户可能希望替换缩略图的内容,比如添加徽章或标签。所以需要在组件中定义插槽,允许用户自定义内容。

然后,编写代码的结构。在ThumbnailList.vue中,使用setup语法,定义props,处理事件。计算每个项的样式,比如宽度、高度、间距。处理点击事件,判断是否禁用,触发select和click事件。图片加载时,触发load事件,失败时触发error事件。

样式部分,使用scoped的style,处理布局的grid,设置列数和间距。可能需要将gutter转换为像素单位,比如如果是数字,默认加px,如果是字符串,直接使用。缩略图的宽高同理。

接下来,测试组件是否符合所有属性和事件的要求。比如,传递items数组,是否正确渲染;itemKey是否正确作为key;columns是否正确设置grid的列数;gutter是否作为间距应用;缩略图的宽高是否生效;占位图是否在加载时显示;选中的索引是否高亮;禁用项是否不可点击;事件是否正确触发等。

最后,提供调用示例。用户需要至少5个示例,展示不同的属性配置,比如基本用法、自定义样式、禁用项、懒加载、自定义插槽等。每个示例需要展示如何传递props,监听事件,以及可能的数据结构。

可能的注意事项包括:处理props的默认值,比如columns默认可能是3,gutter默认0,thumbnailWidth和Height的默认值如何处理;是否需要将数字转换为像素单位;disabledIndexes的数组如何处理,是否使用includes判断;键盘导航的实现细节,比如如何切换焦点,处理方向键事件等。

另外,关于响应式设计,可能需要组件内部根据屏幕宽度调整columns,但用户可能更希望外部控制,所以可能不需要在组件内部处理,而是由父组件通过媒体查询或其他方式传递不同的columns值。

懒加载的实现可能需要为每个图片元素添加data-src属性,然后在进入视口时替换为src。使用Intersection Observer监听每个图片元素,当进入视口时加载图片。这部分需要在onMounted中初始化Observer,并在卸载时断开连接。

动画效果方面,可以为选中状态添加CSS过渡,比如边框颜色变化,或者缩放效果。可能需要使用transition或animation类。

自定义插槽的位置,可能需要在每个缩略图项内部提供一个默认插槽,允许用户覆盖内容。同时,可能需要暴露项的数据给插槽,使用作用域插槽。

现在,开始编写组件代码。首先定义props,使用defineProps函数。然后处理布局的grid样式,计算每个项的宽度和间距。对于gutter,可能需要将其拆分为水平和垂直间距,或者统一处理为gap。使用CSS grid的gap属性,将gutter值传入。比如,如果gutter是20,那么grid-gap: 20px。

缩略图的宽高可能需要在img标签的样式中设置,或者外层容器的尺寸。可能需要将thumbnailWidth和Height转换为带有单位的字符串,比如如果是数字,加上px,否则直接使用。

处理图片加载,使用@load和@error事件,触发对应的事件。同时,在加载过程中显示占位图,直到图片加载完成。

选中的索引通过比较当前项的索引和selectedIndex,添加选中状态的类名。禁用项通过判断索引是否在disabledIndexes数组中,添加禁用样式,并阻止点击事件。

键盘导航部分,需要为每个项添加tabindex,当获得焦点时,监听方向键和回车键。例如,按下右箭头时,移动到下一个项,超出时循环或停止。回车键触发点击事件。这部分可能需要为每个项添加ref,并在键盘事件中切换焦点。

自定义插槽部分,在组件中预留,并将项的数据作为作用域传递,比如:item="item"和:index=“index”,让用户可以在插槽中访问。

最后,编写示例代码,展示不同的使用场景,包括基本用法、自定义样式、禁用项、事件处理、懒加载和插槽自定义。

可能遇到的问题包括:CSS grid布局在不同浏览器中的兼容性,但现代浏览器应该都支持。懒加载的性能问题,需要合理配置Intersection Observer的root和threshold。键盘导航的处理,确保焦点正确切换,并且不会与其他组件冲突。

综上所述,需要分步骤构建组件,处理每个属性和事件,确保功能完整,同时代码结构清晰,符合Vue3组合式API的最佳实践。

📘组件代码

根据你的需求,我为你创建了一个功能完善的缩略图列表组件。以下是完整的实现方案:

<!-- components/ThumbnailList/ThumbnailList.vue -->
<template>
  <ul 
    class="thumbnail-list"
    :style="{
      gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
      gap: typeof gutter === 'number' ? `${gutter}px` : gutter
    }"
  >
    <li
      v-for="(item, index) in items"
      :key="item[itemKey]"
      class="thumbnail-item"
      :class="{
        'selected': index === selectedIndex,
        'disabled': disabledIndexes.includes(index)
      }"
      :style="{
        width: typeof thumbnailWidth === 'number' ? `${thumbnailWidth}px` : thumbnailWidth,
        height: typeof thumbnailHeight === 'number' ? `${thumbnailHeight}px` : thumbnailHeight
      }"
      @click="handleClick(item, index)"
      @keydown="handleKeyDown($event, index)"
      tabindex="0"
    >
      <!-- 自定义插槽 -->
      <slot v-if="$slots.default" :item="item" :index="index"></slot>
      <div v-else class="thumbnail-content">
        <div class="image-wrapper">
          <img
            :src="loadingPlaceholder"
            :data-src="item.src"
            :alt="item.title"
            class="thumbnail-image"
            @load="handleImageLoad(item, index)"
            @error="handleImageError(item, index)"
            ref="imageRefs"
          />
        </div>
        <div v-if="item.title" class="thumbnail-title">{{ item.title }}</div>
        <div v-if="item.description" class="thumbnail-description">{{ item.description }}</div>
      </div>
    </li>
  </ul>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'

const props = defineProps({
  items: {
    type: Array,
    required: true
  },
  itemKey: {
    type: String,
    default: 'id'
  },
  columns: {
    type: Number,
    default: 3
  },
  gutter: {
    type: [Number, String],
    default: 10
  },
  thumbnailWidth: {
    type: [Number, String],
    default: 200
  },
  thumbnailHeight: {
    type: [Number, String],
    default: 150
  },
  loadingPlaceholder: String,
  selectedIndex: {
    type: Number,
    default: -1
  },
  disabledIndexes: {
    type: Array,
    default: () => []
  }
})

const emit = defineEmits(['click', 'load', 'error', 'select'])

const imageRefs = ref([])
let observer = null

// 懒加载处理
onMounted(() => {
  if ('IntersectionObserver' in window) {
    observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target
          img.src = img.dataset.src
          observer.unobserve(img)
        }
      })
    })

    imageRefs.value.forEach(img => observer.observe(img))
  } else {
    // 浏览器不支持时直接加载所有图片
    imageRefs.value.forEach(img => {
      img.src = img.dataset.src
    })
  }
})

onBeforeUnmount(() => {
  if (observer) observer.disconnect()
})

const handleClick = (item, index) => {
  if (props.disabledIndexes.includes(index)) return
  
  emit('click', { item, index })
  emit('select', { item, index })
}

const handleKeyDown = (e, index) => {
  if (props.disabledIndexes.includes(index)) return

  const itemsCount = props.items.length
  switch(e.key) {
    case 'ArrowRight':
      focusItem((index + 1) % itemsCount)
      break
    case 'ArrowLeft':
      focusItem((index - 1 + itemsCount) % itemsCount)
      break
    case 'Enter':
      handleClick(props.items[index], index)
      break
  }
}

const focusItem = (index) => {
  const items = document.querySelectorAll('.thumbnail-item')
  if (items[index]) items[index].focus()
}

const handleImageLoad = (item, index) => {
  emit('load', { item, index })
}

const handleImageError = (item, index) => {
  emit('error', { item, index })
}
</script>

<style scoped>
.thumbnail-list {
  display: grid;
  list-style: none;
  padding: 0;
  margin: 0;
}

.thumbnail-item {
  position: relative;
  cursor: pointer;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  overflow: hidden;
  background: #f5f5f5;
}

.thumbnail-item:hover {
  transform: translateY(-5px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.thumbnail-item.selected {
  outline: 2px solid #2196f3;
  outline-offset: 2px;
}

.thumbnail-item.disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

.image-wrapper {
  position: relative;
  width: 100%;
  height: 80%;
  overflow: hidden;
}

.thumbnail-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 0.3s ease;
}

.thumbnail-image[data-src] {
  opacity: 0;
}

.thumbnail-image.loaded {
  opacity: 1;
}

.thumbnail-title {
  padding: 8px;
  font-weight: bold;
  text-align: center;
}

.thumbnail-description {
  padding: 0 8px 8px;
  font-size: 0.9em;
  text-align: center;
  color: #666;
}

@media (max-width: 768px) {
  .thumbnail-list {
    grid-template-columns: repeat(2, 1fr) !important;
  }
}

@media (max-width: 480px) {
  .thumbnail-list {
    grid-template-columns: 1fr !important;
  }
}
</style>

以下是5个使用示例:

<!-- 基本用法 -->
<ThumbnailList
  :items="[
    { id: 1, src: 'image1.jpg', title: 'Nature' },
    { id: 2, src: 'image2.jpg', title: 'City' }
  ]"
  item-key="id"
  @click="handleClick"
/>

<!-- 自定义尺寸和间距 -->
<ThumbnailList
  :items="items"
  :columns="4"
  :gutter="20"
  thumbnail-width="250"
  thumbnail-height="200"
/>

<!-- 禁用项和选中状态 -->
<ThumbnailList
  :items="items"
  :selected-index="selectedIndex"
  :disabled-indexes="[0, 2]"
  @select="handleSelect"
/>

<!-- 懒加载和占位符 -->
<ThumbnailList
  :items="lazyItems"
  loading-placeholder="/placeholder.jpg"
/>

<!-- 自定义内容插槽 -->
<ThumbnailList :items="items">
  <template #default="{ item }">
    <div class="custom-thumbnail">
      <img :src="item.src" />
      <div class="badge">{{ item.category }}</div>
    </div>
  </template>
</ThumbnailList>

额外建议和特性说明:

  1. 性能优化:内置了Intersection Observer实现的懒加载
  2. 无障碍支持:支持键盘导航(方向键切换,回车触发点击)
  3. 响应式设计:内置移动端响应式布局
  4. 动画效果:悬停浮动效果和加载渐显动画
  5. 错误处理:图片加载失败会自动保留占位图
  6. 样式扩展:通过CSS变量可以自定义主题色等样式

使用建议:

  1. 对于大数据量的列表建议搭配虚拟滚动使用
  2. 可以通过CSS变量覆盖默认样式
  3. 推荐使用WebP格式图片优化加载性能
  4. 复杂交互场景可以使用作用域插槽完全自定义内容

📚代码测试

运行正常,代码示例中需要自己填一些参数

📚填写参数后主要代码

📘定义组件 src\views\ThumbnailListView.vue

<template>
  <div class="thumbnail-list-demo">
    <h2>缩略图列表示例</h2>

    <!-- 示例1:基本用法 -->
    <section class="demo-section">
      <h3>基本用法</h3>
      <!-- 基本用法 -->
      <ThumbnailList

          :items="[
            { id: 1, src: 'https://picsum.photos/300/200?random=1', title: 'Nature' },
            { id: 2, src: 'https://picsum.photos/300/200?random=2', title: 'City' }
          ]"
          thumbnail-width="250"
          item-key="id"
          @click="handleClick"
      />
    </section>

    <!-- 示例2:自定义尺寸和间距 -->
    <section class="demo-section">
      <h3>自定义尺寸和间距</h3>
      <ThumbnailList
          :items="items"
          :columns="4"
          :gutter="20"
          thumbnail-width="250"
          thumbnail-height="200"
      />
    </section>

    <!-- 示例3:选中和禁用状态 -->
    <section class="demo-section">
      <h3>选中和禁用状态</h3>
      <ThumbnailList
          :items="items"
          thumbnail-width="250"
          :selected-index="selectedIndex"
          :disabled-indexes="[0, 2]"
          @select="handleSelect"
      />
    </section>

    <!-- 示例4:懒加载和占位图 -->
    <section class="demo-section">
      <h3>懒加载和占位图</h3>
      <ThumbnailList
          :items="lazyItems"
          :gutter="20"
          thumbnail-width="250"
          loading-placeholder="/placeholder.jpg"
      />
    </section>

    <!-- 示例5:自定义插槽 -->
    <section class="demo-section">
      <h3>自定义插槽</h3>
      <ThumbnailList
          :items="items"
          :columns="3"
          :gutter="20"
          thumbnail-width="250"
      >
        <template #default="{ item }">
          <div class="custom-thumbnail">
            <img :src="item.src" :alt="item.title">
            <div class="custom-overlay">
              <h4>{{ item.title }}</h4>
              <p>{{ item.description }}</p>
            </div>
          </div>
        </template>
      </ThumbnailList>



    </section>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import ThumbnailList from '../components/ThumbnailList/ThumbnailList.vue'

// 图片数据
const items = ref([
  {
    id: 1,
    src: 'https://picsum.photos/300/200?random=3',
    title: '自然风光',
    description: '美丽的自然景观'
  },
  {
    id: 2,
    src: 'https://picsum.photos/300/200?random=4',
    title: '城市建筑',
    description: '现代都市风貌'
  },
  {
    id: 3,
    src: 'https://picsum.photos/300/200?random=5',
    title: '生活场景',
    description: '日常生活瞬间'
  },
  {
    id: 4,
    src: 'https://picsum.photos/300/200?random=6',
    title: '艺术设计',
    description: '创意艺术作品'
  }
])

// 懒加载示例数据
const lazyItems = ref([
  {
    id: 5,
    src: 'https://picsum.photos/300/200?random=7',
    title: '懒加载图片1',
    description: '延迟加载示例'
  },
  {
    id: 6,
    src: 'https://picsum.photos/300/200?random=8',
    title: '懒加载图片2',
    description: '延迟加载示例'
  },
  {
    id: 7,
    src: 'https://picsum.photos/300/200?random=9',
    title: '懒加载图片3',
    description: '延迟加载示例'
  }
])

// 选中状态
const selectedIndex = ref(1)

// 事件处理函数
const handleClick = (event) => {
  console.log('点击缩略图:', event)
}

const handleSelect = (event) => {
  console.log('选择缩略图:', event)
  selectedIndex.value = event.index
}

const handleImageLoad = (event) => {
  console.log('图片加载成功:', event)
}

const handleImageError = (event) => {
  console.log('图片加载失败:', event)
}
</script>

<style scoped>
.thumbnail-list-demo {
  padding: 20px;
  max-width: 1200px;
  margin: 0 auto;
}

h2 {
  color: #2c3e50;
  margin-bottom: 30px;
  text-align: center;
}

.demo-section {
  margin-bottom: 40px;
  padding: 20px;
  border: 1px solid #ebeef5;
  border-radius: 8px;
  background-color: #fff;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}

h3 {
  color: #2c3e50;
  margin-bottom: 20px;
  font-size: 18px;
}

/* 自定义缩略图样式 */
.custom-thumbnail {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: 8px;
}

.custom-thumbnail img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease;
}

.custom-thumbnail:hover img {
  transform: scale(1.1);
}

.custom-overlay {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 15px;
  background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
  color: white;
  transform: translateY(100%);
  transition: transform 0.3s ease;
}

.custom-thumbnail:hover .custom-overlay {
  transform: translateY(0);
}

.custom-overlay h4 {
  margin: 0 0 5px 0;
  font-size: 16px;
}

.custom-overlay p {
  margin: 0;
  font-size: 14px;
  opacity: 0.8;
}
</style>

📚测试代码正常跑通,附其他基本代码

  • 添加路由
  • 页面展示入口

📘编写路由 src\router\index.js

\router\index.js

import { createRouter, createWebHistory } from 'vue-router'
import RightClickMenuView from '../views/RightClickMenuView.vue'
import RangePickerView from '../views/RangePickerView.vue'


const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'progress',
      component:  () => import('../views/ProgressView.vue'),
    },
    {
      path: '/tabs',
      name: 'tabs',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      // 标签页(Tabs)
      component: () => import('../views/TabsView.vue'),
    },
    {
      path: '/accordion',
      name: 'accordion',
      // 折叠面板(Accordion)
      component: () => import('../views/AccordionView.vue'),
    },
    {
      path: '/timeline',
      name: 'timeline',
      // 时间线(Timeline)
      component: () => import('../views/TimelineView.vue'),
    },
    {
      path: '/backToTop',
      name: 'backToTop',
      component: () => import('../views/BackToTopView.vue')
    },
    {
      path: '/notification',
      name: 'notification',
      component: () => import('../views/NotificationView.vue')
    },
    {
      path: '/card',
      name: 'card',
      component: () => import('../views/CardView.vue')
    },
    {
      path: '/infiniteScroll',
      name: 'infiniteScroll',
      component: () => import('../views/InfiniteScrollView.vue')
    },
    {
      path: '/switch',
      name: 'switch',
      component: () => import('../views/SwitchView.vue')
    },
    {
      path: '/sidebar',
      name: 'sidebar',
      component: () => import('../views/SidebarView.vue')
    },
    {
      path: '/breadcrumbs',
      name: 'breadcrumbs',
      component: () => import('../views/BreadcrumbsView.vue')
    },
    {
      path: '/masonryLayout',
      name: 'masonryLayout',
      component: () => import('../views/MasonryLayoutView.vue')
    },
    {
      path: '/rating',
      name: 'rating',
      component: () => import('../views/RatingView.vue')
    },
    {
      path: '/datePicker',
      name: 'datePicker',
      component: () => import('../views/DatePickerView.vue')
    },
    {
      path: '/colorPicker',
      name: 'colorPicker',
      component: () => import('../views/ColorPickerView.vue')
    },
    {
      path: '/rightClickMenu',
      name: 'rightClickMenu',
      component: RightClickMenuView
    },
    {
      path: '/rangePicker',
      name: 'rangePicker',
      component: () => import('../views/RangePickerView.vue')
    },
    {
      path: '/navbar',
      name: 'navbar',
      component: () => import('../views/NavbarView.vue')
    },
    {
      path: '/formValidation',
      name: 'formValidation',
      component: () => import('../views/FormValidationView.vue')
    },
    {
      path: '/copyToClipboard',
      name: 'copyToClipboard',
      component: () => import('../views/CopyToClipboardView.vue')
    },
    {
      path: '/clickAnimations',
      name: 'clickAnimations',
      component: () => import('../views/ClickAnimationsView.vue')
    },
    {
      path: '/thumbnailList',
      name: 'thumbnailList',
      component: () => import('../views/ThumbnailListView.vue')
    }
  ],
})

export default router

📘编写展示入口 src\App.vue

 src\App.vue

<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />
      <nav>
        <RouterLink to="/">Progress</RouterLink>
        <RouterLink to="/tabs">Tabs</RouterLink>
        <RouterLink to="/accordion">Accordion</RouterLink>
        <RouterLink to="/timeline">Timeline</RouterLink>
        <RouterLink to="/backToTop">BackToTop</RouterLink>
        <RouterLink to="/notification">Notification</RouterLink>
        <RouterLink to="/card">Card</RouterLink>
        <RouterLink to="/infiniteScroll">InfiniteScroll</RouterLink>
        <RouterLink to="/switch">Switch</RouterLink>
        <RouterLink to="/sidebar">Sidebar</RouterLink>
        <RouterLink to="/breadcrumbs">Breadcrumbs</RouterLink>
        <RouterLink to="/masonryLayout">MasonryLayout</RouterLink>
        <RouterLink to="/rating">Rating</RouterLink>
        <RouterLink to="/datePicker">DatePicker</RouterLink>
        <RouterLink to="/colorPicker">ColorPicker</RouterLink>
        <RouterLink to="/rightClickMenu">RightClickMenu</RouterLink>
        <RouterLink to="/rangePicker">RangePicker</RouterLink>
        <RouterLink to="/navbar">Navbar</RouterLink>
        <RouterLink to="/formValidation">FormValidation</RouterLink>
        <RouterLink to="/copyToClipboard">CopyToClipboard</RouterLink>
        <RouterLink to="/clickAnimations">ClickAnimations</RouterLink>
        <RouterLink to="/thumbnailList">ThumbnailList</RouterLink>
      </nav>
    </div>
  </header>

  <RouterView />
</template>

<style scoped>
header {
  line-height: 1.5;
  max-height: 100vh;
}

.logo {
  display: block;
  margin: 0 auto 2rem;
}

nav {
  width: 100%;
  font-size: 12px;
  text-align: center;
  margin-top: 2rem;
}

nav a.router-link-exact-active {
  color: var(--color-text);
}

nav a.router-link-exact-active:hover {
  background-color: transparent;
}

nav a {
  display: inline-block;
  padding: 0 1rem;
  border-left: 1px solid var(--color-border);
}

nav a:first-of-type {
  border: 0;
}

@media (min-width: 1024px) {
  header {
    display: flex;
    place-items: center;
    padding-right: calc(var(--section-gap) / 2);
  }

  .logo {
    margin: 0 2rem 0 0;
  }

  header .wrapper {
    display: flex;
    place-items: flex-start;
    flex-wrap: wrap;
  }

  nav {
    text-align: left;
    margin-left: -1rem;
    font-size: 1rem;

    padding: 1rem 0;
    margin-top: 1rem;
  }
}
</style>

📚页面效果

页面效果

📚相关文章

 

———— 相 关 文 章 ————

 

  1. 0基础3步部署自己的DeepSeek安装步骤

  2. DeepSeek 助力 Vue 开发:打造丝滑的步骤条(Step bar)https://blog.csdn.net/qq_33650655/article/details/145560497

  3. DeepSeek 助力 Vue 开发:打造丝滑的进度条(Progress Bar)https://blog.csdn.net/qq_33650655/article/details/145577034

  4. 自己部署 DeepSeek 助力 Vue 开发:打造丝滑的标签页(Tabs)https://blog.csdn.net/qq_33650655/article/details/145587999

  5. 自己部署 DeepSeek 助力 Vue 开发:打造丝滑的折叠面板(Accordion)https://blog.csdn.net/qq_33650655/article/details/145590404

  6. 自己部署 DeepSeek 助力 Vue 开发:打造丝滑的时间线(Timeline )https://blog.csdn.net/qq_33650655/article/details/145597372

  7. DeepSeek 助力 Vue 开发:打造丝滑的返回顶部按钮(Back to Top)https://blog.csdn.net/qq_33650655/article/details/145615550

  8. DeepSeek 助力 Vue 开发:打造丝滑的通知栏(Notification Bar)https://blog.csdn.net/qq_33650655/article/details/145620055

  9. DeepSeek 助力 Vue 开发:打造丝滑的卡片(Card)https://blog.csdn.net/qq_33650655/article/details/145634564

  10. DeepSeek 助力 Vue 开发:打造丝滑的无限滚动(Infinite Scroll)https://blog.csdn.net/qq_33650655/article/details/145638452

  11. DeepSeek 助力 Vue 开发:打造丝滑的开关切换(Switch)https://blog.csdn.net/qq_33650655/article/details/145644151

  12. DeepSeek 助力 Vue 开发:打造丝滑的侧边栏(Sidebar)https://blog.csdn.net/qq_33650655/article/details/145654204

  13. DeepSeek 助力 Vue 开发:打造丝滑的面包屑导航(Breadcrumbs)https://blog.csdn.net/qq_33650655/article/details/145656895

  14. DeepSeek 助力 Vue 开发:打造丝滑的瀑布流布局(Masonry Layout)https://blog.csdn.net/qq_33650655/article/details/145663699

  15. DeepSeek 助力 Vue 开发:打造丝滑的评分组件(Rating)https://blog.csdn.net/qq_33650655/article/details/145664576

  16. DeepSeek 助力 Vue 开发:打造丝滑的日期选择器(Date Picker),未使用第三方插件 https://blog.csdn.net/qq_33650655/article/details/145673279

  17. DeepSeek 助力 Vue 开发:打造丝滑的颜色选择器(Color Picker)https://blog.csdn.net/qq_33650655/article/details/145689522

  18. DeepSeek 助力 Vue 开发:打造丝滑的右键菜单(RightClickMenu)https://blog.csdn.net/qq_33650655/article/details/145706658

  19. DeepSeek 助力 Vue 开发:打造丝滑的范围选择器(Range Picker)https://blog.csdn.net/qq_33650655/article/details/145713572

  20. DeepSeek 助力 Vue 开发:打造丝滑的导航栏(Navbar)https://blog.csdn.net/qq_33650655/article/details/145732421

  21. DeepSeek 助力 Vue 开发:打造丝滑的表单验证(Form Validation)https://blog.csdn.net/qq_33650655/article/details/145735582

  22. DeepSeek 助力 Vue 开发:打造丝滑的复制到剪贴板(Copy to Clipboard)https://blog.csdn.net/qq_33650655/article/details/145739569

  23. DeepSeek 助力 Vue 开发:打造丝滑的点击动画(Click Animations)https://blog.csdn.net/qq_33650655/article/details/145766184

到此这篇文章就介绍到这了,更多精彩内容请关注本人以前的文章或继续浏览下面的文章,创作不易,如果能帮助到大家,希望大家多多支持宝码香车~💕,若转载本文,一定注明本文链接。


整理不易,点赞关注宝码香车

更多专栏订阅推荐:
👍 html+css+js 绚丽效果
💕 vue
✈️ Electron
⭐️ js
📝 字符串
✍️ 时间对象(Date())操作


http://www.kler.cn/a/556089.html

相关文章:

  • 【数据库系统概论】第6章 (三)数据依赖的公理系统
  • 深度解析应用层协议-----HTTP与MQTT(涵盖Paho库)
  • 【UCB CS 61B SP24】Lecture 4 - Lists 2: SLLists学习笔记
  • Linux----线程
  • SpringBoot核心框架之AOP详解
  • 拆解微软CEO纳德拉战略蓝图:AI、量子计算、游戏革命如何改写未来规则!
  • 【WRF模拟】全过程总结:更换不同研究时段改动总结
  • 宝塔mysql8.0设置数据库区分大小写
  • 宝塔怎么搭建LiveHelperChat?PHP开源在线客服安装教程
  • Lua C API :lua_insert 函数详解
  • WPS携手DeepSeek:开启智能办公新时代
  • Linux基础开发工具的使用(apt、vim、gcc、g++、gdb、make、makefile)
  • 计算机网络面试知识点总结
  • 开源免费文档翻译工具 可支持pdf、word、excel、ppt
  • java练习(36)
  • Qt中使用QPdfWriter类结合QPainter类绘制并输出PDF文件
  • 15增减字符串匹配(贪心)思路解析+源码
  • WPS接入deepseek-OfficeAI助手插件下载
  • 武汉火影数字|VR沉浸式空间制作 VR大空间打造
  • Spring面试题2