<template v-for
, ячейки с rowspan'ами создаются в зависимости от равенства индексов элементов вложенных массивов нулю, значения rowspan'ов - длины (суммы длин) вложенных массивов. Например:const rowspan = item => item.backlinks.reduce((acc, n) => acc + n.recipients.length, 0);
<tbody>
<template v-for="item in data">
<template v-for="(backlink, iBacklink) in item.backlinks">
<template v-for="(recipient, iRecipient) in backlink.recipients">
<tr>
<template v-if="!iBacklink && !iRecipient">
<td :rowspan="rowspan(item)">{{ item.name }}</td>
<td :rowspan="rowspan(item)">{{ item['domain score'] }}</td>
</template>
<template v-if="!iRecipient">
<td :rowspan="backlink.recipients.length">{{ backlink.donor }}</td>
<td :rowspan="backlink.recipients.length">{{ backlink['page score'] }}</td>
</template>
<td>{{ recipient.url }}</td>
<td>{{ recipient.image }}</td>
</tr>
</template>
</template>
</template>
</tbody>
const keys = ref([
'name', 'domain score', 'backlinks',
'donor', 'page score', 'recipients',
'url', 'image',
]);
function createTableData(arr, keys, iKey = 0) {
return arr.flatMap(n => {
const row = [];
const innerRows = [];
for (let i = iKey; i < keys.length; i++) {
const val = n[keys[i]];
if (Array.isArray(val)) {
innerRows.push(...createTableData(val, keys, i + 1));
row.forEach(cell => cell.rowspan = innerRows.length);
row.push(...innerRows.shift());
break;
} else {
row.push({ text: val });
}
}
return [ row, ...innerRows ];
});
}
const tableData = computed(() => {
return createTableData(props.data, props.keys);
});
<tbody>
<tr v-for="row in tableData">
<td
v-for="cell in row"
v-text="cell.text"
:rowspan="cell.rowspan"
></td>
</tr>
</tbody>
const coords = reactive([ 50, 50 ]);
const circleStyles = computed(() => ({
left: `${coords[0]}px`,
top: `${coords[1]}px`,
}));
const updateCoords = e => (coords[0] += e.movementX, coords[1] += e.movementY);
const updateCoordsOn = () => document.addEventListener('mousemove', updateCoords);
const updateCoordsOff = () => document.removeEventListener('mousemove', updateCoords);
<div
class="circle"
:style="circleStyles"
@mousedown="updateCoordsOn"
@mouseup="updateCoordsOff"
></div>
<div v-once>
<select ref="select" v-model="region"></select>
</div>
mounted() {
const choices = new Choices(this.$refs.select);
this.$watch(
'regions',
val => choices.setChoices(val, 'value', 'name', true),
{ immediate: true }
);
this.$on('hook:beforeDestroy', () => choices.destroy());
},
- <script src="https://unpkg.com/vue-router@3/dist/vue-router.js"></script>
+ <script src="https://unpkg.com/vue-router@4/dist/vue-router.global.js"></script>
import VueRouter from 'vue-router'
<yandex-map
ref="map"
@map-was-initialized="onBoundsChange"
@boundschange="onBoundsChange"
...
>
<ymap-marker
v-for="n in markersData"
...
>
methods: {
onBoundsChange() {
const bounds = this.$refs.map.myMap.getBounds();
this.markersData.forEach(n => {
if (
bounds[0][0] < n.coords[0] && n.coords[0] < bounds[1][0] &&
bounds[0][1] < n.coords[1] && n.coords[1] < bounds[1][1]
) {
// ...
}
});
},
...
метод testemit компонента question не отрабатывает
$emit('testemit')
есть, а вот @testemit="testemit"
отсутствует. const lastEdited = Vue.observable({ instance: null });
props: [ 'value' ],
methods: {
onInput(e) {
this.$emit('input', e.target.value);
lastEdited.instance = this;
},
},
created() {
this.$watch(() => lastEdited.instance, val => {
if (val && val !== this) {
this.$emit('input', '');
}
});
},
beforeDestroy() {
if (lastEdited.instance === this) {
lastEdited.instance = null;
}
},
<input :value="value" @input="onInput">
const eventBus = new Vue();
props: [ 'value' ],
methods: {
onInput(e) {
this.$emit('input', e.target.value);
eventBus.$emit('clear-other-inputs', this);
},
},
created() {
const clear = e => e !== this && this.$emit('input', '');
eventBus.$on('clear-other-inputs', clear);
this.$on('hook:beforeDestroy', () => eventBus.$off('clear-other-inputs', clear));
},
<input :value="value" @input="onInput">
имеется pinia такого вида
<...>
export const useSearchStore = () => {
const { search, updateSearchQuery } = useSearchStore();
Note thatstore
is an object wrapped withreactive
, meaning there is no need to write.value
after getters but, likeprops
insetup
, we cannot destructure it
const useSearchStore = defineStore('search', () => {
const search = ref('');
const setSearch = val => search.value = val;
return { search, setSearch };
});
const searchStore = useSearchStore();
const search = computed({
get: () => searchStore.search,
set: searchStore.setSearch,
});
<input v-model.trim="search">
const searchStore = useSearchStore();
watch(() => searchStore.search, val => console.log(val));
animation-delay
в зависимость от индекса в v-for
:v-for="(n, i) in items"
:style="{ 'animation-delay': i * 0.5 + 's' }"
индекс используется для других целей, там текстовая строка, а не число. В этом и сложность задачи
v-for
для объектов выдаёт три значения, как раз для того, чтобы у свойств тоже мог быть индекс. <select>
<template v-for="n in options">
<option>
<slot name="option" :option="n">{{ n }}</slot>
</option>
</template>
</select>
<v-select :options="countries">
<template #option="{ option }">{{ option.name }}</template>
</v-select>
vue3-mq
this.$mq
$mq has been removed as a global property and must now be injected into components that require it
const createTopic = () => ({
text: '',
subtopics: [ createSubTopic() ],
});
const createSubTopic = () => ({
text: '',
});
const topics = reactive([ createTopic() ]);
<div v-for="(topic, iTopic) in topics">
<div>
<div>
Topic #{{ iTopic + 1 }}:
<input v-model="topic.text">
</div>
<div v-for="(subtopic, iSubtopic) in topic.subtopics">
Subtopic #{{ iTopic + 1 }}.{{ iSubtopic + 1 }}:
<input v-model="subtopic.text">
<button v-if="iSubtopic" @click="topic.subtopics.splice(iSubtopic, 1)">x</button>
</div>
</div>
<button @click="topic.subtopics.push(createSubTopic())">Add subtopic</button>
</div>
<button @click="topics.push(createTopic())">Add topic</button>