Карточки в слайдер

생성일: 2025년 1월 24일

답변에 사용된 모델 O1 by Chat01

질문

есть код компонента : <template>
<v-container>
<v-row justify="center">
<v-col
cols="9"
xxl="2"
xl="2"
lg="3"
md="3"
sm="3"
v-for="(i, index) in internetTariffs"
:key="index"
>
<v-card :class="['pa-3 price-card card-animate', getTariffClass(i.name)]">
<v-card-subtitle class="text-center title mb-3">
<v-icon large :color="getIconColor(i.name)" class="mr-2">{{ i.icon }}</v-icon>
<span>{{ i.name }}</span>
</v-card-subtitle>

<v-card-subtitle class="text-center mb-4 price-container">
<div class="price d-flex justify-center align-center">
<span class="currency">₴</span>
<span class="amount">{{ i.price }}</span>
<span class="frequency">/ {{ $t("prices.month") }}</span>
</div>
<div class="speed text-h6 mt-2">
<v-icon color="green darken-3" size="24">mdi-web</v-icon>
{{ $t("prices.up-to") }} {{ i.speed }} {{ $t("prices.Mbps") }}
</div>
</v-card-subtitle>

<v-divider></v-divider>

<v-card-text v-if="i.features.some(f => f !== '')" class="text-center features-list pa-2">
<v-list dense>
<v-list-item v-for="(f, index) in i.features" :key="index">
<v-icon color="primary" class="mr-2">mdi-check-circle-outline</v-icon>
<span>{{ f }}</span>
</v-list-item>
</v-list>
</v-card-text>

<v-switch
class="blue-label iptv-switch"
v-model="i.externalIpEnabled"
hide-details
inset
color="teal-lighten-1"
:label="`${$t('prices.external-ip')}`"
></v-switch>

<template v-if="i.iptv">
<v-switch
class="blue-label iptv-switch"
v-model="iptvEnabled"
hide-details
inset
color="teal-lighten-1"
label="+ IPTV"
>
<template v-slot:append>
<v-img
lazy-src="@/assets/prices/logo_sweettv_light.svg"
min-height="70"
min-width="70"
max-height="75"
max-width="75"
src="@/assets/prices/logo_sweettv_light.svg"
contain
></v-img>
</template>
</v-switch>

<v-radio-group v-model="selectedIptvTariff" v-if="iptvEnabled">
<v-radio
v-for="tariff in iptvTariffs"
:key="tariff.id"
:label="tariff.name + tariff.tv_count + ' TV'"
:value="tariff"
color="teal-lighten-1"
></v-radio>
</v-radio-group>
</template>

<v-alert v-if="totalPrice(i) >= 0" color="teal-lighten-1" class="total-price-alert">
{{ $t("prices.total") }}: {{ i.price + totalPrice(i) }} ₴/{{ $t("prices.month") }}
</v-alert>

<v-card-actions class="justify-center">
<RequestForm
:FormData="getFormData(i)"
:ButtonTitle="$t('prices.to-buy')"
ButtonColor="teal-lighten-1"
/>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</template>

<script>
export default {
data() {
return {
iptvEnabled: false,
selectedIptvTariff: null,
internetTariffs: [
{
id: 1,
name: "«Економ»",
speed: 50,
price: 200,
iptv: false,
icon: "mdi-microsoft-internet-explorer",
features: [],
externalIpPrice: 75,
externalIpEnabled: false,
},
{
id: 2,
name: "«Стандарт»",
speed: 100,
price: 250,
iptv: true,
icon: "mdi-wifi",
features: [],
externalIpPrice: 75,
externalIpEnabled: false,
},
{
id: 3,
name: "«Люкс»",
speed: 1000,
price: 350,
iptv: true,
icon: "mdi-wifi-star",
features: [],
externalIpPrice: 0,
externalIpEnabled: true,
},
],
iptvTariffs: [
{ id: 1, name: "S ", tv_count: 245, price: 80 },
{ id: 1, name: "M ", tv_count: 333, price: 120 },
{ id: 2, name: "L ", tv_count: 347, price: 180 },
],
};
},
watch: {
iptvEnabled(iptvEnabled) {
const currentScroll = window.scrollY;
if (iptvEnabled) {
window.scrollTo({
top: currentScroll + 110,
behavior: "smooth",
});
} else {
window.scrollTo({
top: currentScroll - 110,
behavior: "smooth",
});
}
}
},
methods: {
totalPrice(tariff) {
let total = 0;
if (tariff.externalIpEnabled) total += tariff.externalIpPrice;
if (tariff.iptv && this.iptvEnabled && this.selectedIptvTariff) total += this.selectedIptvTariff.price;
return total;
},
getTariffClass(name) {
switch (name) {
case "«Стандарт»":
return "standard-tariff";
case "«Люкс»":
return "lux-tariff";
case "«Економ»":
return "economy-tariff";
default:
return "";
}
},
getIconColor(name) {
switch (name) {
case "«Стандарт»":
return "blue";
case "«Люкс»":
return "#ff8f00";
case "«Економ»":
return "green";
default:
return "primary";
}
},
getFormData(tariff) {
const selectedFeatures = tariff.features.filter((f) => f).join(", ");

// Проверка на наличие IPTV в тарифе, чтобы не включать его в расчёт, если он не поддерживается
const iptvText = tariff.iptv && this.iptvEnabled && this.selectedIptvTariff
? ` + IPTV (${this.selectedIptvTariff.name})`
: "";

// Проверка на наличие externalIp в тарифе, чтобы добавить эту услугу, если она включена
const externalIpText = tariff.externalIpEnabled
? this.$t('prices.external-ip')
: "";

// Формируем строку данных для передачи, добавляя информацию о тарифах, функциях, IPTV и externalIp, если они доступны
return `${this.$t("prices.rates")}: ${tariff.name} ${selectedFeatures}${iptvText} ${externalIpText} = ${this.$t("prices.total")}: ₴${tariff.price + this.totalPrice(tariff)} / ${this.$t("prices.month")}`;
}


},
};
</script>

<style scoped>
.card-animate {
transition: all 0.3s ease;
}
.card-animate-out {
transform: scale(0.95);
opacity: 0.7;
}
.card-animate:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transform: scale(1.02);
}

.features-list {
background-color: transparent;
}

.v-btn {
font-size: 1rem;
text-transform: none;
}

:deep(.v-input__control) {
display: block;
}
:deep(.v-selection-control .v-label) {
align-items: start !important;
}

:deep(.blue-label .v-label) {
font-size: 1.08rem;
font-weight: 500;
font-family: Roboto-Medium;
}

:deep(.iptv-switch .v-input__control) {
margin-bottom: 8px;
}

.v-card-subtitle.title {
font-size: 1.25rem;
font-weight: bold;
color: black;
}
</style>
мне надо чтобы прайсы сохранили свою логику и дизайн но были в виде слайдера

사고

Switching layout approach

The user is keen on converting their Vuetify-based card layout into a slider, exploring options like <v-carousel> or <v-slide-group>, and considering a more advanced custom library such as Swiper.

Crafting the slider

I’m working through a Vuetify slider using <v-carousel> and <v-carousel-item>, maintaining the existing card logic and design, and ensuring elements like rows are streamlined.

Utilizing Vuetify 2

I’m piecing together a <v-carousel> example, ensuring arrow navigation and card alignment, while maintaining Vuetify 2</v-carousel> features and logic. Progress is evident in maintaining consistency with Vuetify 2.3.

Thought about slider implementation for pricing for 12 seconds

답변

Ниже приведён один из вариантов, как можно превратить ваши карточки в слайдер, используя встроенный в Vuetify компонент <v-carousel>. Принцип такой: вместо ряда с колонками (<v-row>, <v-col> для каждой карточки) мы используем слайдер <v-carousel> и помещаем каждую карточку в отдельный <v-carousel-item>.

Обратите внимание:

  1. Если вам нужно, чтобы на экране одновременно было видно несколько карточек (например, три колонки), можно обернуть каждую карточку в <v-col> и настроить стили/ширину так, чтобы карусель могла пролистывать их «поштучно» или группами.
  2. В данном примере у каждого <v-carousel-item> будет одна карточка, занимающая всю ширину слайдера.
  3. Логику и стили карточки мы полностью сохраняем, лишь перемещаем её в <v-carousel-item>.

Пример кода

<template> <v-container fluid> <!-- Обёртка самого слайдера --> <v-carousel show-arrows cycle height="auto" hide-delimiter-background class="my-5" > <!-- Генерация слайдов по данным из массива internetTariffs --> <v-carousel-item v-for="(tariff, index) in internetTariffs" :key="index" > <!-- Можно обернуть в row/col, если нужно центрировать карточки в рамках слайда --> <v-row justify="center"> <v-col cols="12" sm="8" md="6" lg="4" > <v-card :class="['pa-3 price-card card-animate', getTariffClass(tariff.name)]"> <v-card-subtitle class="text-center title mb-3"> <v-icon large :color="getIconColor(tariff.name)" class="mr-2" > {{ tariff.icon }} </v-icon> <span>{{ tariff.name }}</span> </v-card-subtitle> <v-card-subtitle class="text-center mb-4 price-container"> <div class="price d-flex justify-center align-center"> <span class="currency">&#8372;</span> <span class="amount">{{ tariff.price }}</span> <span class="frequency">/ {{ $t("prices.month") }}</span> </div> <div class="speed text-h6 mt-2"> <v-icon color="green darken-3" size="24">mdi-web</v-icon> {{ $t("prices.up-to") }} {{ tariff.speed }} {{ $t("prices.Mbps") }} </div> </v-card-subtitle> <v-divider></v-divider> <v-card-text v-if="tariff.features.some(f => f !== '')" class="text-center features-list pa-2" > <v-list dense> <v-list-item v-for="(f, featureIndex) in tariff.features" :key="featureIndex" > <v-icon color="primary" class="mr-2"> mdi-check-circle-outline </v-icon> <span>{{ f }}</span> </v-list-item> </v-list> </v-card-text> <v-switch class="blue-label iptv-switch" v-model="tariff.externalIpEnabled" hide-details inset color="teal-lighten-1" :label="`${$t('prices.external-ip')}`" ></v-switch> <template v-if="tariff.iptv"> <v-switch class="blue-label iptv-switch" v-model="iptvEnabled" hide-details inset color="teal-lighten-1" label="+ IPTV" > <template v-slot:append> <v-img lazy-src="@/assets/prices/logo_sweettv_light.svg" min-height="70" min-width="70" max-height="75" max-width="75" src="@/assets/prices/logo_sweettv_light.svg" contain ></v-img> </template> </v-switch> <v-radio-group v-model="selectedIptvTariff" v-if="iptvEnabled" > <v-radio v-for="tariffItem in iptvTariffs" :key="tariffItem.id" :label="tariffItem.name + tariffItem.tv_count + ' TV'" :value="tariffItem" color="teal-lighten-1" ></v-radio> </v-radio-group> </template> <!-- Итоговая цена --> <v-alert v-if="totalPrice(tariff) >= 0" color="teal-lighten-1" class="total-price-alert" > {{ $t("prices.total") }}: {{ tariff.price + totalPrice(tariff) }} &#8372;/{{ $t("prices.month") }} </v-alert> <v-card-actions class="justify-center"> <RequestForm :FormData="getFormData(tariff)" :ButtonTitle="$t('prices.to-buy')" ButtonColor="teal-lighten-1" /> </v-card-actions> </v-card> </v-col> </v-row> </v-carousel-item> </v-carousel> </v-container> </template> <script> export default { data() { return { iptvEnabled: false, selectedIptvTariff: null, internetTariffs: [ { id: 1, name: "&laquo;Економ&raquo;", speed: 50, price: 200, iptv: false, icon: "mdi-microsoft-internet-explorer", features: [], externalIpPrice: 75, externalIpEnabled: false, }, { id: 2, name: "&laquo;Стандарт&raquo;", speed: 100, price: 250, iptv: true, icon: "mdi-wifi", features: [], externalIpPrice: 75, externalIpEnabled: false, }, { id: 3, name: "&laquo;Люкс&raquo;", speed: 1000, price: 350, iptv: true, icon: "mdi-wifi-star", features: [], externalIpPrice: 0, externalIpEnabled: true, }, ], iptvTariffs: [ { id: 1, name: "S ", tv_count: 245, price: 80 }, { id: 1, name: "M ", tv_count: 333, price: 120 }, { id: 2, name: "L ", tv_count: 347, price: 180 }, ], }; }, watch: { iptvEnabled(iptvEnabled) { // Логика скролла при переключении iptvEnabled const currentScroll = window.scrollY; if (iptvEnabled) { window.scrollTo({ top: currentScroll + 110, behavior: "smooth", }); } else { window.scrollTo({ top: currentScroll - 110, behavior: "smooth", }); } } }, methods: { totalPrice(tariff) { let total = 0; if (tariff.externalIpEnabled) total += tariff.externalIpPrice; if (tariff.iptv && this.iptvEnabled && this.selectedIptvTariff) { total += this.selectedIptvTariff.price; } return total; }, getTariffClass(name) { switch (name) { case "&laquo;Стандарт&raquo;": return "standard-tariff"; case "&laquo;Люкс&raquo;": return "lux-tariff"; case "&laquo;Економ&raquo;": return "economy-tariff"; default: return ""; } }, getIconColor(name) { switch (name) { case "&laquo;Стандарт&raquo;": return "blue"; case "&laquo;Люкс&raquo;": return "#ff8f00"; case "&laquo;Економ&raquo;": return "green"; default: return "primary"; } }, getFormData(tariff) { const selectedFeatures = tariff.features.filter((f) => f).join(", "); const iptvText = tariff.iptv && this.iptvEnabled && this.selectedIptvTariff ? ` + IPTV (${this.selectedIptvTariff.name})` : ""; const externalIpText = tariff.externalIpEnabled ? this.$t('prices.external-ip') : ""; return `${this.$t("prices.rates")}: ${tariff.name} ${selectedFeatures}${iptvText} ${externalIpText} = ${this.$t("prices.total")}: &#8372;${tariff.price + this.totalPrice(tariff)} / ${this.$t("prices.month")}`; }, }, }; </script> <style scoped> .card-animate { transition: all 0.3s ease; } .card-animate-out { transform: scale(0.95); opacity: 0.7; } .card-animate:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); transform: scale(1.02); } .features-list { background-color: transparent; } .v-btn { font-size: 1rem; text-transform: none; } /* Следующие стили, где используется :deep, нужны для переопределения внутренних элементов Vuetify (v-input, v-label и т.д.) */ :deep(.v-input__control) { display: block; } :deep(.v-selection-control .v-label) { align-items: start !important; } :deep(.blue-label .v-label) { font-size: 1.08rem; font-weight: 500; font-family: Roboto-Medium; } :deep(.iptv-switch .v-input__control) { margin-bottom: 8px; } .v-card-subtitle.title { font-size: 1.25rem; font-weight: bold; color: black; } /* Пример адаптации высоты слайдера для карточек */ .v-carousel-item { display: flex !important; align-items: center; justify-content: center; } </style>

Что произошло в коде:

  1. Используем <v-carousel> вместо контейнера/строки для карточек.
  2. Каждую карточку рендерим внутри <v-carousel-item> — это делает её отдельным «слайдом».
  3. Сами карточки и их контент остаются практически без изменений, логика и стили сохранены.
  4. При необходимости отцентрировать карточку в слайде — используем <v-row justify="center"> и <v-col>.

Так вы получите слайдер, в котором каждая карточка (тариф) — это один из «слайдов» карусели. Если хотите, чтобы на одном слайде выводилось сразу несколько тарифов (например, по 3 штуки), можно настроить ширину карточек и использовать, к примеру, <v-col cols="12" md="4"> и хранить несколько карточек в одном <v-carousel-item>.

Таким образом вы сохраните и внешний вид, и логику, но вместо статичного ряда тарифов получите возможность их пролистывать.

질문

очень плохо с весткой стрелочки слайдера надо расположить над слайдаими сверху и можно стилизировать их убрать кружки сделай стильно и адапртивно

사고

Crafting carousel control

I’m detailing a carousel layout with arrows at top corners, removing pagination dots. Styling involves using an arrow slot or overriding default styles, ensuring a polished look.

Refining carousel navigation

Tweaking arrow positions and hiding dots. Styling involves setting top to 20px, pointer-events to auto, and tweaking background and before content for custom icons. This boosts usability and aesthetics.

Crafting custom carousel controls

I’m setting up a carousel with top corner arrows, using SCSS for styling overrides. Adjustments include arrow positioning, top alignment, size settings, and ensuring responsiveness.

Tweaking icons

I’m setting icon font size to 30px and color to white. Considering minimal styling with semi-transparent backgrounds and ensuring icons are clickable. This leads to a more intuitive navigation.

Adjusting carousel controls

I’m working on aligning carousel controls at the top, tweaking their styling. Ensuring responsiveness and clickable icons is key. This leads me to fine-tune the visual dynamics.

Thought about slider arrow customization for 14 seconds

답변

Ниже приведён пример, как можно превратить ваши карточки в слайдер и при этом красиво разместить стрелки навигации в верхней части (над слайдами), убрать «точки» (делимитеры) и сделать более «стильный» внешний вид.

Основные моменты:

  1. Отображаем стрелки через проп show-arrows.
  2. Убираем «точки» (делимитеры) через проп hide-delimiters.
  3. Стилизация положения стрелок с помощью селекторов .v-carousel__controls, .v-carousel__prev, .v-carousel__next и т.д.
  4. Делаем саму высоту слайдера «автоматической», чтобы карточки не урезались, указав height="auto".

Пример кода

<template> <v-container fluid> <!-- Слайдер --> <v-carousel cycle height="auto" class="styled-carousel my-5" show-arrows hide-delimiters hide-delimiter-background > <!-- Генерация слайдов по данным из массива internetTariffs --> <v-carousel-item v-for="(tariff, index) in internetTariffs" :key="index" > <v-row justify="center" no-gutters> <v-col cols="12" sm="8" md="6" lg="4" > <v-card :class="['pa-3 price-card card-animate', getTariffClass(tariff.name)]"> <v-card-subtitle class="text-center title mb-3"> <v-icon large :color="getIconColor(tariff.name)" class="mr-2" > {{ tariff.icon }} </v-icon> <span>{{ tariff.name }}</span> </v-card-subtitle> <v-card-subtitle class="text-center mb-4 price-container"> <div class="price d-flex justify-center align-center"> <span class="currency">&#8372;</span> <span class="amount">{{ tariff.price }}</span> <span class="frequency">/ {{ $t("prices.month") }}</span> </div> <div class="speed text-h6 mt-2"> <v-icon color="green darken-3" size="24">mdi-web</v-icon> {{ $t("prices.up-to") }} {{ tariff.speed }} {{ $t("prices.Mbps") }} </div> </v-card-subtitle> <v-divider></v-divider> <v-card-text v-if="tariff.features.some(f => f !== '')" class="text-center features-list pa-2" > <v-list dense> <v-list-item v-for="(f, featureIndex) in tariff.features" :key="featureIndex" > <v-icon color="primary" class="mr-2"> mdi-check-circle-outline </v-icon> <span>{{ f }}</span> </v-list-item> </v-list> </v-card-text> <v-switch class="blue-label iptv-switch" v-model="tariff.externalIpEnabled" hide-details inset color="teal-lighten-1" :label="`${$t('prices.external-ip')}`" ></v-switch> <template v-if="tariff.iptv"> <v-switch class="blue-label iptv-switch" v-model="iptvEnabled" hide-details inset color="teal-lighten-1" label="+ IPTV" > <template v-slot:append> <v-img lazy-src="@/assets/prices/logo_sweettv_light.svg" min-height="70" min-width="70" max-height="75" max-width="75" src="@/assets/prices/logo_sweettv_light.svg" contain ></v-img> </template> </v-switch> <v-radio-group v-model="selectedIptvTariff" v-if="iptvEnabled" > <v-radio v-for="tariffItem in iptvTariffs" :key="tariffItem.id" :label="tariffItem.name + tariffItem.tv_count + ' TV'" :value="tariffItem" color="teal-lighten-1" ></v-radio> </v-radio-group> </template> <!-- Итоговая цена --> <v-alert v-if="totalPrice(tariff) >= 0" color="teal-lighten-1" class="total-price-alert" > {{ $t("prices.total") }}: {{ tariff.price + totalPrice(tariff) }} &#8372;/{{ $t("prices.month") }} </v-alert> <v-card-actions class="justify-center"> <RequestForm :FormData="getFormData(tariff)" :ButtonTitle="$t('prices.to-buy')" ButtonColor="teal-lighten-1" /> </v-card-actions> </v-card> </v-col> </v-row> </v-carousel-item> </v-carousel> </v-container> </template> <script> export default { data() { return { iptvEnabled: false, selectedIptvTariff: null, internetTariffs: [ { id: 1, name: "&laquo;Економ&raquo;", speed: 50, price: 200, iptv: false, icon: "mdi-microsoft-internet-explorer", features: [], externalIpPrice: 75, externalIpEnabled: false, }, { id: 2, name: "&laquo;Стандарт&raquo;", speed: 100, price: 250, iptv: true, icon: "mdi-wifi", features: [], externalIpPrice: 75, externalIpEnabled: false, }, { id: 3, name: "&laquo;Люкс&raquo;", speed: 1000, price: 350, iptv: true, icon: "mdi-wifi-star", features: [], externalIpPrice: 0, externalIpEnabled: true, }, ], iptvTariffs: [ { id: 1, name: "S ", tv_count: 245, price: 80 }, { id: 1, name: "M ", tv_count: 333, price: 120 }, { id: 2, name: "L ", tv_count: 347, price: 180 }, ], }; }, watch: { iptvEnabled(iptvEnabled) { // Логика скролла при переключении iptvEnabled const currentScroll = window.scrollY; if (iptvEnabled) { window.scrollTo({ top: currentScroll + 110, behavior: "smooth", }); } else { window.scrollTo({ top: currentScroll - 110, behavior: "smooth", }); } }, }, methods: { totalPrice(tariff) { let total = 0; if (tariff.externalIpEnabled) { total += tariff.externalIpPrice; } if (tariff.iptv && this.iptvEnabled && this.selectedIptvTariff) { total += this.selectedIptvTariff.price; } return total; }, getTariffClass(name) { switch (name) { case "&laquo;Стандарт&raquo;": return "standard-tariff"; case "&laquo;Люкс&raquo;": return "lux-tariff"; case "&laquo;Економ&raquo;": return "economy-tariff"; default: return ""; } }, getIconColor(name) { switch (name) { case "&laquo;Стандарт&raquo;": return "blue"; case "&laquo;Люкс&raquo;": return "#ff8f00"; case "&laquo;Економ&raquo;": return "green"; default: return "primary"; } }, getFormData(tariff) { const selectedFeatures = tariff.features.filter((f) => f).join(", "); const iptvText = tariff.iptv && this.iptvEnabled && this.selectedIptvTariff ? ` + IPTV (${this.selectedIptvTariff.name})` : ""; const externalIpText = tariff.externalIpEnabled ? this.$t("prices.external-ip") : ""; return `${this.$t("prices.rates")}: ${tariff.name} ${selectedFeatures}${iptvText} ${externalIpText} = ${this.$t( "prices.total" )}: &#8372;${tariff.price + this.totalPrice(tariff)} / ${this.$t( "prices.month" )}`; }, }, }; </script> <style scoped> /* Карточки */ .card-animate { transition: all 0.3s ease; } .card-animate:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); transform: scale(1.02); } .features-list { background-color: transparent; } .v-btn { font-size: 1rem; text-transform: none; } :deep(.v-input__control) { display: block; } :deep(.v-selection-control .v-label) { align-items: start !important; } :deep(.blue-label .v-label) { font-size: 1.08rem; font-weight: 500; font-family: Roboto-Medium; } :deep(.iptv-switch .v-input__control) { margin-bottom: 8px; } .v-card-subtitle.title { font-size: 1.25rem; font-weight: bold; color: black; } /* ==== Стилизация карусели ==== */ .styled-carousel { position: relative; /* Убираем &laquo;градиентный&raquo; фон по умолчанию на точках */ .v-carousel__controls__delimiter { display: none; } /* Контейнер для стрелок - делаем его вверху */ .v-carousel__controls { top: 0; bottom: auto; left: 0; right: 0; height: 60px; background: linear-gradient(to bottom, rgba(0, 0, 0, 0.4), transparent); display: flex; justify-content: space-between; align-items: center; } /* Сами кнопки (стрелки) */ .v-carousel__prev, .v-carousel__next { position: static; transform: none; /* Если нужно скруглённое полупрозрачное обрамление при ховере, можно так: border-radius: 50%; &:hover { background: rgba(255, 255, 255, 0.2); } */ } /* Иконки стрелок: меняем размер, цвет и т.д. */ .v-carousel__prev .v-icon, .v-carousel__next .v-icon { font-size: 32px; color: #fff; /* Светлые, чтобы выделялись на тёмном фоне */ } } /* Для адаптивности у нас всё уже "резиновое": - <v-container fluid> - <v-row no-gutters> - <v-col ...> Если нужно больше конкретики, можно добавлять медиа-запросы. */ </style>

Пояснения:

  1. Сами карточки практически не изменились – мы лишь завернули их в <v-carousel> / <v-carousel-item>, чтобы превратить обычный список тарифов в слайдер.
  2. Основные настройки карусели:
    • show-arrows — показывает стрелки навигации «вперёд/назад».
    • hide-delimiters и hide-delimiter-background — убирают индикаторы («точки») и фоновую полоску под ними.
    • cycle — делает так, чтобы слайды листались «по кругу».
    • height="auto" — позволяет карточкам подстраивать высоту в зависимости от их контента.
  3. Стили для стрелок расположены в верхней части карусели через .v-carousel__controls { top: 0; ... }.
    • При желании вы можете подправлять отступы, фон, цвет стрелок и т.д.
    • Также можно добавить hover-эффект или свою иконку, заменяя стандартные иконки с помощью дополнительных CSS или через кастомные слоты (но в Vuetify 2.x это чуть сложнее и обычно проще переопределять через стили).
  4. Адаптивность достигается за счёт того, что мы используем fluid-контейнер, v-row и v-col. Для разных размеров экранов можно менять ширину колонки (к примеру, lg="4", md="6", sm="8", xs="12" и т.д.).

Таким образом, вы получите стильный, адаптивный, компактный слайдер с тарифами, где стрелки размещены сверху, а «точки» навигации скрыты.

이 Q&A 공유하기