v-model
. v-for
, которому будет передаваться массив с данными. Блок базовой комплектации примет примерно такой вид:baseOptions: [
{ name: 'Свойство 1', price: 10000 },
{ name: 'Свойство 2', price: 11000 },
...
<ul>
<li v-for="n in baseOptions">{{ n.name }} ({{ n.price }})</li>
</ul>
v-model
):extraOptions: [
{ name: 'Пакет 1', price: 16000, checked: false },
{ name: 'Пакет 2', price: 17000, checked: false },
...
<div v-for="n in extraOptions">
<input type="checkbox" v-model="n.checked">
<label>{{ n.name }}</label>
</div>
methods: {
sum: arr => arr.reduce((acc, n) => acc + n.price, 0),
computed: {
baseSum() {
return this.sum(this.baseOptions);
},
extraSum() {
return this.sum(this.extraOptions.filter(n => n.checked));
},
filters: {
price: val => `${val.toLocaleString()} р.`,
<span>{{ baseSum | price }}</span>
<span>{{ extraSum | price }}</span>
<gmap-info-window
:options="infoOptions"
:position="infoWindowPos"
:opened="infoWinOpen"
@closeclick="infoWinOpen = false"
>
{{ infoContent }}
</gmap-info-window>
onMarkerClick(e) {
this.infoWindowPos = e.latLng; // задаём положение окна, над кликнутым маркером
this.infoContent = JSON.stringify(e.latLng); // задаём контент окна, передаётся в слот
this.infoWinOpen = true; // открываем окно
},
created() {
const onClickOutside = e => this.opened = this.$el.contains(e.target) && this.opened;
document.addEventListener('click', onClickOutside);
this.$on('hook:beforeDestroy', () => document.removeEventListener('click', onClickOutside));
},
const getMonthName = iMonth =>
new Date(0, iMonth).toLocaleString('ru-RU', { month: 'long' });
computed: {
groupedByMonth() {
return this.items.reduce((acc, n) => {
const key = getMonthName(n.date.split('-')[1] - 1);
(acc[key] = acc[key] || []).push(n);
return acc;
}, {});
},
},
<ul>
<li v-for="(items, month) in groupedByMonth" :key="month">
<h3>{{ month }}</h3>
<ul>
<li v-for="n in items" :key="n.id">{{ n }}</li>
</ul>
</li>
</ul>
groupedByMonth() {
return this.items.reduce((acc, n) => {
acc[n.date.split('-')[1] - 1].items.push(n);
return acc;
}, Array.from({ length: 12 }, (_, i) => ({
month: getMonthName(i),
items: [],
})));
},
<ul>
<li v-for="{ items, month } in groupedByMonth" :key="month">
<h3>{{ month }}</h3>
<ul v-if="items.length">
<li v-for="n in items" :key="n.id">{{ n }}</li>
</ul>
<div v-else>данных нет</div>
</li>
</ul>
import router from './router.js';
const url = `/api/photos?${в зависимости от router.currentRoute}=true&page=${list}&limit=15`;
<gmap-map
ref="map"
v-bind="options"
@click="onMapClick"
>
<gmap-marker
v-for="m in markers"
:key="m.id"
:position="m.position"
:clickable="true"
:draggable="true"
@click="onMarkerClick"
/>
</gmap-map>
data: () => ({
options: {
center: { lat: 45.101637, lng: 38.986345 },
zoom: 15,
},
markers: [],
}),
methods: {
onMapClick(e) {
this.markers.push({
id: 1 + Math.max(0, ...this.markers.map(n => n.id)),
position: e.latLng,
});
},
onMarkerClick(e) {
this.$refs.map.panTo(e.latLng);
// или
// this.options.center = e.latLng;
},
},
v-model
свойства checked элементов products. Отключить чекбокс - свойства disabled у вас уже есть, просто привяжите их значения к чекбоксам.<input
type="checkbox"
v-model="product.checked"
:disabled="product.disabled"
>
computed: {
totalPrice() {
return this.products.reduce((acc, n) => acc + n.checked * n.price, 0);
},
},
state: {
page: 0,
perPage: 5,
total: 0,
posts: [],
loading: false,
},
getters: {
numPages: state => Math.ceil(state.total / state.perPage),
},
mutations: {
updateLoading: (state, loading) => state.loading = loading,
updatePosts: (state, { posts, total, page }) => Object.assign(state, { posts, total, page }),
},
actions: {
async fetchPosts({ state, commit }, page) {
commit('updateLoading', true);
const start = (page - 1) * state.perPage;
const end = page * state.perPage;
const url = `https://jsonplaceholder.typicode.com/posts?_start=${start}&_end=${end}`;
try {
const response = await fetch(url);
const posts = await response.json();
const total = response.headers.get('x-total-count');
commit('updatePosts', { posts, total, page });
} catch (e) {
console.error(e);
}
commit('updateLoading', false);
},
},
props: [ 'page', 'numPages' ],
methods: {
goTo(page) {
this.$emit('paginate', page);
},
next(step) {
this.goTo(this.page + step);
},
},
computed: {
isFirst() {
return this.page <= 1;
},
isLast() {
return this.page >= this.numPages;
},
},
<button @click="goTo(1)" :disabled="isFirst"><<</button>
<button @click="next(-1)" :disabled="isFirst"><</button>
{{ page }} / {{ numPages }}
<button @click="next(+1)" :disabled="isLast">></button>
<button @click="goTo(numPages)" :disabled="isLast">>></button>
v-model
:model: {
prop: 'page',
event: 'paginate',
},
computed: {
page: {
get() {
return this.$store.state.page;
},
set(page) {
this.$store.dispatch('fetchPosts', page);
},
},
<компонент-пагинации
v-model="page"
:num-pages="$store.getters.numPages"
/>
created() {
this.page = 1;
},
<div v-if="$store.state.loading">данные загружаются, ждите</div>
<компонент-для-отображения-данных v-else :данные="$store.state.posts" />
выводит побуквенно
v-for
так и перебирает - "побуквенно". То есть, значениями user являются строки единичной длины. Ну а свойств id, name, price и т.д. у строк нет, отсюда пустота там, где вы выводите свойства элементов users. mounted() {
this.cropper = new Cropper(this.$refs.image, {
zoomable: false,
scalable: false,
aspectRatio: 1,
});
},
methods: {
selectFile(e) {
const file = (e.target.files || e.dataTransfer.files)[0];
if (file) {
const reader = new FileReader();
reader.onload = e => this.cropper.replace(e.target.result);
reader.readAsDataURL(file);
}
},
},
methods: {
onPopupOpen(e) {
e.target
.getPopup()
.getElement()
.querySelector('.leaflet-popup-content-wrapper')
.addEventListener('dblclick', this.onPopupDblClick);
},
onPopupDblClick(e) {
const
id = +e.currentTarget.querySelector('[data-id]').dataset.id,
// теперь, зная id, можно найти соответствующий объект
...
.once('popupopen', this.onPopupOpen)