使用Quasar Carousel时如何按间隔实现v-for循环
实现自动分组的博客轮播与分页联动
核心思路
通过计算属性将博客数组按每3篇一组拆分,再用v-for自动生成轮播幻灯片,同时将分页组件的总页数绑定到分组后的数组长度,实现数据驱动的动态渲染,后续新增博客无需手动修改模板。
具体修改步骤
1. 添加分组计算属性
在<script setup>中新增计算属性,将posts数组拆分为每3个元素一组的二维数组:
import { ref, computed, watch } from 'vue' // ... 原有代码 const postsPerSlide = 3 const groupedPosts = computed(() => { const groups = [] for (let i = 0; i < posts.length; i += postsPerSlide) { groups.push(posts.slice(i, i + postsPerSlide)) } return groups })
2. 动态生成轮播幻灯片
修改<q-carousel>内部代码,遍历groupedPosts自动生成幻灯片:
<q-carousel v-model="slide" transition-prev="slide-right" transition-next="slide-left" animated control-color="primary" class="full-height full-width" > <q-carousel-slide v-for="(group, index) in groupedPosts" :key="index" :name="(index + 1).toString()" class="column no-wrap flex-center" > <div class="tw-mx-auto tw-mt-16 tw-grid tw-max-w-2xl tw-grid-cols-1 tw-gap-x-8 tw-gap-y-20 lg:tw-mx-0 lg:tw-max-w-none lg:tw-grid-cols-3"> <article v-for="post in group" :key="post.id" class="tw-flex tw-flex-col tw-items-start tw-justify-between"> <!-- 原有文章内容代码保持不变 --> </article> </div> </q-carousel-slide> </q-carousel>
3. 联动分页组件
修改分页组件的max属性,绑定到分组数组的长度,确保页码数量动态匹配:
<q-pagination v-model="currentPage" :max="groupedPosts.length" direction-links />
4. 优化双向同步逻辑
添加反向监听,让轮播切换时同步更新分页页码:
watch(currentPage, (currentValue) => { slide.value = currentValue.toString(); }) watch(slide, (val) => { currentPage.value = parseInt(val) })
完整修改后的代码
<template> <div class="skew-cc"></div> <div class="tw-bg-white tw-py-24 sm:tw-py-32"> <div class="tw-mx-auto tw-max-w-7xl tw-px-6 lg:tw-px-8"> <div class="service-block q-pa-lg text-center column no-wrap flex-center block-wrapper" id="Blog"> <h2 class="text-h2 text-weight-bolder text-dark q-mb-xs">Useful Articles</h2> <h6 class="text-h6 text-weight-light text-dark q-my-none" style="max-width: 800px;">We hope to share some of our knowledge to help you make an informed decision!</h6> </div> <q-carousel v-model="slide" transition-prev="slide-right" transition-next="slide-left" animated control-color="primary" class="full-height full-width" > <q-carousel-slide v-for="(group, index) in groupedPosts" :key="index" :name="(index + 1).toString()" class="column no-wrap flex-center" > <div class="tw-mx-auto tw-mt-16 tw-grid tw-max-w-2xl tw-grid-cols-1 tw-gap-x-8 tw-gap-y-20 lg:tw-mx-0 lg:tw-max-w-none lg:tw-grid-cols-3"> <article v-for="post in group" :key="post.id" class="tw-flex tw-flex-col tw-items-start tw-justify-between"> <div class="tw-relative tw-w-full"> <q-img :src="post.imageUrl" alt="" class="aspect-[16/9] w-full rounded-2xl bg-gray-100 object-cover sm:aspect-[2/1] lg:aspect-[3/2]" /> <div class="absolute inset-0 rounded-2xl ring-1 ring-inset ring-gray-900/10" /> </div> <div class="tw-max-w-xl"> <div class="tw-mt-8 tw-flex tw-items-center tw-gap-x-4 tw-text-xs"> <time :datetime="post.datetime" class="tw-text-gray-500">{{ post.date }}</time> <a :href="post.category.href" class="tw-relative tw-z-10 tw-rounded-full tw-bg-gray-50 tw-px-3 tw-py-1.5 tw-font-medium tw-text-gray-600 hover:tw-bg-gray-100">{{ post.category.title }}</a> </div> <div class="tw-group tw-relative"> <h3 class="tw-mt-3 tw-text-lg tw-font-semibold tw-leading-6 tw-text-gray-900 group-hover:tw-text-gray-600"> <a :href="post.href"> <span class="tw-absolute tw-inset-0" /> {{ post.title }} </a> </h3> <p class="tw-mt-5 tw-line-clamp-3 tw-text-sm tw-leading-6 tw-text-gray-600">{{ post.description }}</p> </div> <div class="tw-relative tw-mt-8 tw-flex tw-items-center tw-gap-x-4"> <img :src="post.author.imageUrl" alt="" class="tw-h-10 tw-w-10 tw-rounded-full tw-bg-gray-100" /> <div class="tw-text-sm tw-leading-6"> <p class="tw-font-semibold tw-text-gray-900"> <a :href="post.author.href"> <span class="tw-absolute tw-inset-0" /> {{ post.author.name }} </a> </p> <p class="tw-text-gray-600">{{ post.author.role }}</p> </div> </div> </div> </article> </div> </q-carousel-slide> </q-carousel> <div class="q-pa-lg q-mt-xl flex flex-center"> <q-pagination v-model="currentPage" :max="groupedPosts.length" direction-links /> </div> </div> </div> </template> <script setup> import { ref, computed, watch } from 'vue' const currentPage = ref(1); const slide = ref('1'); const posts = [ { id: 1, title: 'Common Electrical Problems In Your Home that You Should Leave it to the Professionals', href: '#', description: "Electricity is the jolt that keeps everything buzzing. In the electrifying world of homeownership, dealing with electrical issues can be a wild experience. Wires snaking through walls, charges ready to zap, and the fear of being plunged into darkness – it's enough to make your hair stand on end! But fear not, for you're not alone. We're here to shed some light on them.", imageUrl: '../assets/imgs/blog/post 1.jpg', date: 'July 5, 2023', datetime: '2023-07-05', category: { title: 'Electrical Repair', href: '#' }, author: { name: 'Bill Griffin', role: 'Owner / Master Electrician', href: '#', imageUrl: 'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80', }, }, { id: 2, title: '6 Shockingly Good Reasons to Upgrade AFCI and GFCI Circuit Breakers', href: '#', description: "Circuit breakers prevent electrical fires and protect individuals from electric shocks. While GFCIs circuit breakers protect against shocking encounters, AFCI circuit breakers put on their fire hats to extinguish potential blazes. The moment they sense a ground fault, they hit the brakes on the electrical flow, keeping you safe from harm. It's a dynamic duo working together to keep your home safe and sound.", imageUrl: '../assets/imgs/blog/post 2.jpeg', date: 'July 7, 2023', datetime: '2023-07-07', category: { title: 'Electrical Upgrades', href: '#' }, author: { name: 'Bill Griffin', role: 'Owner / Master Electrician', href: '#', imageUrl: 'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80', }, }, { id: 3, title: 'Why Everyone Needs A Generator Backup Hookup for Continuous Power Supply', href: '#', description: "Power outages can disrupt our lives in numerous ways, causing inconvenience and potentially hazardous situations. In Harford County, Maryland, where severe weather events are uncommon, a generator backup hookup can be a game-changer. Last year, Harford County experienced 12,370 outages with BGE and 36 outages with Delmarva Power. By investing in a generator hookup, Harford County residents can protect themselves and their properties from the consequences of weather-related power outages. In this blog post, we will see the worst-case scenarios of losing power, discuss what a generator backup hookup is, how to connect it and shed light on the prevalence of weather-related power outages in Harford County.", imageUrl: '../assets/imgs/blog/post 3.jpeg', date: 'July 9, 2023', datetime: '2023-07-09', category: { title: 'Electrical Upgrades', href: '#' }, author: { name: 'Chris Whiteford', role: 'Owner / Master Electrician', href: '#', imageUrl: 'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80', }, }, // 新增博客文章会自动分组,无需修改模板 ] const postsPerSlide = 3 const groupedPosts = computed(() => { const groups = [] for (let i = 0; i < posts.length; i += postsPerSlide) { groups.push(posts.slice(i, i + postsPerSlide)) } return groups }) watch(currentPage, (currentValue) => { slide.value = currentValue.toString(); }) watch(slide, (val) => { currentPage.value = parseInt(val) }) </script> <style lang="scss" scoped> .block-wrapper{ max-width: 1024px; margin-left: auto; margin-right: auto; } .block-wrapper-xl{ max-width: 1600px; margin-left: auto; margin-right: auto; } /*dividor code for mini t shirt*/ .divider { /* minor cosmetics */ display: table; font-size: 24px; text-align: center; width: 75%; /* divider width */ margin: 10px auto 40px; /* spacing above/below */ } .divider span { display: table-cell; position: relative; } .divider span:first-child, .divider span:last-child { width: 50%; top: 13px; /* adjust vertical align */ -moz-background-size: 100% 2px; /* line width */ background-size: 100% 2px; /* line width */ background-position: 0 0, 0 100%; background-repeat: no-repeat; } .divider span:first-child { /* color changes in here */ background-image: -webkit-gradient( linear, 0 0, 0 100%, from(transparent), to(#000) ); background-image: -webkit-linear-gradient(180deg, transparent, $dark); background-image: -moz-linear-gradient(180deg, transparent, $dark); background-image: -o-linear-gradient(180deg, transparent, $dark); background-image: linear-gradient(90deg, transparent, $dark); } .divider span:nth-child(2) { color: $dark; padding: 0px 5px; width: auto; white-space: nowrap; } .divider span:last-child { /* color changes in here */ background-image: -webkit-gradient( linear, 0 0, 0 100%, from($dark), to(transparent) ); background-image: -webkit-linear-gradient(180deg, $dark, transparent); background-image: -moz-linear-gradient(180deg, $dark, transparent); background-image: -o-linear-gradient(180deg, $dark, transparent); background-image: linear-gradient(90deg, $dark, transparent); } /*diagonal dividor markup*/ .colour-block { background:#F5F5F5; width:100%; padding-top:100px; color:#fff; } .white-block { background:#fff; width:100%; padding-top:100px; color:#F5F5F5; } /*------------------*/ /* SKEW CLOCKWISE */ /*------------------*/ .skew-cc{ width:100%; height:100px; position:absolute; left:0px; background: linear-gradient(to right bottom, #F5F5F5 49%, #fff 50%), linear-gradient(-50deg, #ffffff 16px, #000 0); } /*-------------------------*/ /* SKEW COUNTER CLOCKWISE */ /*-------------------------*/ .skew-c{ width:100%; height:100px; position:absolute; left:0px; background: linear-gradient(to left bottom, #fff 49%, #F5F5F5 50%); } .blog-blurb{ line-height: 2; } </style>
功能说明
groupedPosts计算属性会自动根据博客数组长度拆分分组,每3篇为一组,新增博客后分组自动更新。- 轮播幻灯片通过
v-for动态生成,每个幻灯片的name与分页页码一一对应。 - 分页组件的总页数绑定到分组数组长度,无需手动设置固定值。
- 双向监听确保轮播切换和分页点击时,两者状态同步更新。
内容的提问来源于stack exchange,提问作者Qiuzman
相关产品推荐
相关产品推荐

