I am building a website with a lot of items. So the path is like:
example.com/catalog/indoor/fireplace/awesome-one
And currently I am creating hundreds and hundreds of Vue
instances:
AwesomeOne.vue
AnotherOne.vue
YetAnotherThing.vue
...
AhundredthThing.vue
And all they have inside is a filtered array and passing a prop to the reusable component which uses data from Vuex
storage.
And so I am thinking, is there a way to avoid creating so many Vue
files? I kinda sorted the problem with Nuxt.js
, but it only automatically generates routes which makes the development slightly easier. But I still have to create a lot of files with nearly identical code inside (it's literally just 3 words difference: the name of the computed property, the filter option and the name of the prop).
I was thinking about some computed property which would assign an URL dynamically, using data from Vuex.state
. But I can't put things together.
as requested posting the code: Catalog.vue
<template>
<v-main>
<v-row>
<v-col
v-for="category in categories"
:key="category.i"
>
<v-card
router
:to="category.link"
>
<v-img
height="250"
width="250"
:src="category.img"
/>
<v-card-title>
{{ category.name }}
</v-card-title>
</v-card>
</v-col>
</v-row>
</v-main>
</template>
<script>
export default {
computed: {
categories () {
return this.$store.state.categories
}
}
}
</script>
CatalogCategories.vue (having 5 of them)
<template>
<v-sheet>
<h1>Изделия для дома</h1>
<v-row>
<v-col
v-for="indoorItem in indoorItems"
:key="indoorItem.i"
>
<a :href="indoorItem.link">{{ indoorItem.name }}</a>
</v-col>
</v-row>
</v-sheet>
</template>
<script>
export default {
computed: {
subCategories () {
return this.$store.state.subCategories
},
indoorItems () {
return this.subCategories.filter((category) => category.type === "indoor" || category.type === "bothdoor");
},
}
}
</script>
Banquet.vue (one of the categories, having over 20 of them)
<template>
<v-sheet>
<h1>Банкетки</h1>
<item-view :items="this.banquet" />
</v-sheet>
</template>
<script>
import ItemView from "../../../components/ItemView.vue"
export default {
components: { ItemView },
computed: {
items () {
return this.$store.state.items
},
banquet () {
return this.items.filter((item) => item.type === "banquet");
},
}
}
</script>
ItemView.Vue (reusable component)
<template>
<v-sheet>
<v-row>
<v-col
v-for="item in items"
:key="item.i"
>
<v-card
route
:to="item.path"
>
<v-img
width="250"
height="250"
:src="item.img"
/>
<v-card-title> {{ item.name }} </v-card-title>
<v-card-subtitle> Цена: {{ item.price }} грамм конфет му-му </v-card-subtitle>
</v-card>
</v-col>
</v-row>
</v-sheet>
</template>
<script>
export default {
props: {
items: {
type: Array,
required: true,
},
},
}
</script>
IndividualItem.vue (I'll have to create over 100 of them)
<template>
<v-sheet>
<v-card>
<individual-item-view :items="this.obossana" />
<v-card />
</v-card>
</v-sheet>
</template>
<script>
import IndividualItemView from '../../../../components/IndividualItemView.vue'
export default {
components: { IndividualItemView },
computed: {
items () {
return this.$store.state.items
},
obossana () {
return this.items.filter((item) => item.title === "obossana");
},
}
}
</script>
<style>
</style>
IndividualItemView.vue (reusable component)
<template>
<v-sheet>
<v-card
v-for="item in items"
:key="item.i"
>
<v-card-title> {{ item.name }} </v-card-title>
<expandable-image
class="image"
close-on-background-click
:src="item.img"
/>
<v-card-subtitle> Цена: {{ item.price }} раз послать тебя ко всем чертям </v-card-subtitle>
<v-card-text> {{ item.description }} </v-card-text>
</v-card>
</v-sheet>
</template>
<script>
export default {
mounted() {
const viewportMeta = document.createElement('meta');
viewportMeta.name = 'viewport';
viewportMeta.content = 'width=device-width, initial-scale=1';
document.head.appendChild(viewportMeta);
},
props: {
items: {
type: Array,
required: true,
},
},
}
</script>
<style scoped>
.image {
width: 400px;
max-width: 100%;
}
</style>
And little snippet from Vuex store:
{
name: "Обоссаная банкетка",
title: "obossana",
type: "banquet",
path: "/catalog/indoor/banquet/obossana",
price: "215361",
img:
"https://b2.3ddd.ru/media/cache/tuk_model_custom_filter_ru/model_images/0000/0000/0079/79546.53239b3804d0a.jpeg",
description: "blah blah blah",
},
Alright I figured it out.
_view.vue
for individual item view:
<script>
export default {
async asyncData() {
const items = await fetch(
'API URL here'
).then((res) => res.json())
return { items }
},
data() {
return {
params: this.$route.params.view,
title: '',
}
},
computed: {
filteredItems() {
return this.items.filter((item) => item.title === this.$route.params.view)
},
},
}
</script>
turned out it was pretty easy. All I had to do is to fetch the hard coded data (I decided to use API because in the future I'll have to add more items and I believe it gonna be easier if I use API) and filter it that the item.title
matches the route $route.params.view
Inside the template
tag (_view.vue
file) I had:
<template>
<v-main>
<h1>API: items</h1>
<h2>'/catalog/_subcategory/_individualitem/_view.vue' here</h2>
<h3>params.view: {{ params }}</h3>
<v-card v-for="item in filteredItems" :key="item.i">
<v-avatar size="200">
<img :src="item.img" alt="картинка предмета" />
</v-avatar>
<v-card-title>
{{ item.name }} and item.title: {{ item.title }}
</v-card-title>
<v-card-subtitle>Цена: {{ item.price }} сантиметров</v-card-subtitle>
<v-card-text> {{ item.description }} </v-card-text>
</v-card>
</v-main>
</template>
h1
, h2
, h3
tags helped me to figure out (by displaying) values of variables.
The file tree is (the root is catalog itself):
├── index.vue
├── _subcategory
│ ├── _individualitem
│ │ └── _view.vue
│ └── _items.vue
└── _type.vue
And a little bit of api.json
:
{
"title": "stremnaya-arka",
"path": "/catalog/outdoor/arcs/stremnaya-arka",
"name": "Стрёмная арка",
"type": "arcs",
}
Might be a bit confusing with the file names, but I hope it is not.
I hope it'll help some other newbie who struggles as much as me ;)