前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕
目录
- 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_12">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。
功能要有,如下属性:
属性定义
数据相关属性
-
items
- 类型:数组
- 描述:用于存储缩略图列表的数据,数组中的每个元素代表一个缩略图项,可以包含图片的
src
、标题、描述等信息。 - 示例:
[{ src: 'image1.jpg', title: 'Image 1' }, { src: 'image2.jpg', title: 'Image 2' }]
-
itemKey
- 类型:字符串
- 描述:指定
items
数组中每个元素的唯一标识字段,用于 Vue 的:key
绑定,确保列表渲染的高效性和正确性。 - 示例:如果
items
数组中的每个元素有一个id
字段作为唯一标识,则itemKey
可以设置为'id'
。
布局相关属性
-
columns
- 类型:数字
- 描述:指定缩略图列表的列数,用于控制布局。
- 示例:
3
表示列表将显示为 3 列。
-
gutter
- 类型:数字或字符串
- 描述:缩略图项之间的间距,可以是像素值(如
20
)或带有单位的值(如'20px'
)。
样式相关属性
-
thumbnailWidth
- 类型:数字或字符串
- 描述:指定缩略图的宽度,可以是像素值(如
100
)或带有单位的值(如'100px'
)。
-
thumbnailHeight
- 类型:数字或字符串
- 描述:指定缩略图的高度,可以是像素值(如
100
)或带有单位的值(如'100px'
)。
-
loadingPlaceholder
- 类型:字符串
- 描述:图片加载过程中显示的占位图的
src
地址。
交互相关属性
-
selectedIndex
- 类型:数字
- 描述:指定当前选中的缩略图项的索引,用于实现选中状态的高亮显示。
-
disabledIndexes
- 类型:数组
- 描述:指定禁用的缩略图项的索引数组,禁用的项不可点击。
- 示例:
[1, 3]
表示索引为 1 和 3 的缩略图项被禁用。
事件
-
click
- 参数:当前点击的缩略图项的数据和索引
- 描述:当用户点击缩略图项时触发,可用于实现点击缩略图的相关操作,如显示大图、跳转到详情页等。
-
load
- 参数:当前加载成功的缩略图项的数据和索引
- 描述:当缩略图图片加载成功时触发,可用于实现加载成功后的特效或统计加载成功的图片数量。
-
error
- 参数:当前加载失败的缩略图项的数据和索引
- 描述:当缩略图图片加载失败时触发,可用于显示加载失败的提示信息或替换为默认图片。
-
select
- 参数:当前选中的缩略图项的数据和索引
- 描述:当缩略图项被选中时触发,可用于更新
selectedIndex
属性或执行其他选中相关的操作。
其他
- 响应式设计:考虑使用 CSS 媒体查询或 Vue 的响应式布局方案,使缩略图列表在不同屏幕尺寸下都能有良好的显示效果。
- 懒加载:对于大量的缩略图列表,使用懒加载技术可以提高页面的加载性能,只在缩略图进入可视区域时才加载图片。
- 键盘导航:支持键盘操作,如使用方向键选择缩略图项,回车键触发点击事件,提高组件的可访问性。
- 动画效果:为缩略图的选中、加载等状态添加适当的动画效果,提升用户体验。
- 自定义插槽:提供自定义插槽,允许用户自定义缩略图的内容,如在缩略图上添加自定义的徽章、标签等。
你有更好的建议也可以添加,要注明。组件定义好后给出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>javascript">
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>
额外建议和特性说明:
- 性能优化:内置了Intersection Observer实现的懒加载
- 无障碍支持:支持键盘导航(方向键切换,回车触发点击)
- 响应式设计:内置移动端响应式布局
- 动画效果:悬停浮动效果和加载渐显动画
- 错误处理:图片加载失败会自动保留占位图
- 样式扩展:通过CSS变量可以自定义主题色等样式
使用建议:
- 对于大数据量的列表建议搭配虚拟滚动使用
- 可以通过CSS变量覆盖默认样式
- 推荐使用WebP格式图片优化加载性能
- 复杂交互场景可以使用作用域插槽完全自定义内容
📚代码测试
运行正常,代码示例中需要自己填一些参数
📚填写参数后主要代码
📘定义组件 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>javascript">
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
javascript">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
<script setup>javascript">
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>
📚页面效果
📚相关文章
-
0基础3步部署自己的DeepSeek安装步骤
-
DeepSeek 助力 Vue 开发:打造丝滑的步骤条(Step bar)https://blog.csdn.net/qq_33650655/article/details/145560497
-
DeepSeek 助力 Vue 开发:打造丝滑的进度条(Progress Bar)https://blog.csdn.net/qq_33650655/article/details/145577034
-
自己部署 DeepSeek 助力 Vue 开发:打造丝滑的标签页(Tabs)https://blog.csdn.net/qq_33650655/article/details/145587999
-
自己部署 DeepSeek 助力 Vue 开发:打造丝滑的折叠面板(Accordion)https://blog.csdn.net/qq_33650655/article/details/145590404
-
自己部署 DeepSeek 助力 Vue 开发:打造丝滑的时间线(Timeline )https://blog.csdn.net/qq_33650655/article/details/145597372
-
DeepSeek 助力 Vue 开发:打造丝滑的返回顶部按钮(Back to Top)https://blog.csdn.net/qq_33650655/article/details/145615550
-
DeepSeek 助力 Vue 开发:打造丝滑的通知栏(Notification Bar)https://blog.csdn.net/qq_33650655/article/details/145620055
-
DeepSeek 助力 Vue 开发:打造丝滑的卡片(Card)https://blog.csdn.net/qq_33650655/article/details/145634564
-
DeepSeek 助力 Vue 开发:打造丝滑的无限滚动(Infinite Scroll)https://blog.csdn.net/qq_33650655/article/details/145638452
-
DeepSeek 助力 Vue 开发:打造丝滑的开关切换(Switch)https://blog.csdn.net/qq_33650655/article/details/145644151
-
DeepSeek 助力 Vue 开发:打造丝滑的侧边栏(Sidebar)https://blog.csdn.net/qq_33650655/article/details/145654204
-
DeepSeek 助力 Vue 开发:打造丝滑的面包屑导航(Breadcrumbs)https://blog.csdn.net/qq_33650655/article/details/145656895
-
DeepSeek 助力 Vue 开发:打造丝滑的瀑布流布局(Masonry Layout)https://blog.csdn.net/qq_33650655/article/details/145663699
-
DeepSeek 助力 Vue 开发:打造丝滑的评分组件(Rating)https://blog.csdn.net/qq_33650655/article/details/145664576
-
DeepSeek 助力 Vue 开发:打造丝滑的日期选择器(Date Picker),未使用第三方插件 https://blog.csdn.net/qq_33650655/article/details/145673279
-
DeepSeek 助力 Vue 开发:打造丝滑的颜色选择器(Color Picker)https://blog.csdn.net/qq_33650655/article/details/145689522
-
DeepSeek 助力 Vue 开发:打造丝滑的右键菜单(RightClickMenu)https://blog.csdn.net/qq_33650655/article/details/145706658
-
DeepSeek 助力 Vue 开发:打造丝滑的范围选择器(Range Picker)https://blog.csdn.net/qq_33650655/article/details/145713572
-
DeepSeek 助力 Vue 开发:打造丝滑的导航栏(Navbar)https://blog.csdn.net/qq_33650655/article/details/145732421
-
DeepSeek 助力 Vue 开发:打造丝滑的表单验证(Form Validation)https://blog.csdn.net/qq_33650655/article/details/145735582
-
DeepSeek 助力 Vue 开发:打造丝滑的复制到剪贴板(Copy to Clipboard)https://blog.csdn.net/qq_33650655/article/details/145739569
-
DeepSeek 助力 Vue 开发:打造丝滑的点击动画(Click Animations)https://blog.csdn.net/qq_33650655/article/details/145766184
到此这篇文章就介绍到这了,更多精彩内容请关注本人以前的文章或继续浏览下面的文章,创作不易,如果能帮助到大家,希望大家多多支持宝码香车~💕,若转载本文,一定注明本文链接。
更多专栏订阅推荐:
👍 html+css+js 绚丽效果
💕 vue
✈️ Electron
⭐️ js
📝 字符串
✍️ 时间对象(Date())操作