
Bagian pertama di sini
Kami terus mengembangkan toko online kami. Bagian ini adalah:
- pemuatan gambar normal pada alamat statis
- remah roti yang dihasilkan klien
- halaman produk
- topi
- beli tombol kerja dengan sinkronisasi barang antar tab (dan sesi)
Unsplash . ( ). .
""
.
const products = require('../static/mock/products.json')
const got = require('got')
const QS = require('querystring')
const API_KEY = ''
const fs = require('fs')
const { promisify } = require('util')
const writeFileAsync = promisify(fs.writeFile)
async function fetchApiImg (searchQuery) {
try {
const query = QS.stringify({ key: API_KEY, q: searchQuery, per_page: '3', image_type: 'photo' })
const resPr = got(`https://pixabay.com/api/?${query}`)
const json = await resPr.json()
if (json.hits && json.hits.length > 0 && json.hits[0].largeImageURL && json.hits[0].webformatURL) {
return {
imgXL: json.hits[0].largeImageURL,
imgL: json.hits[0].webformatURL
}
} else {
throw 'no image'
}
} catch (error) {
return {
imgXL: null,
imgL: null
}
}
}
async function getImagesUrls () {
let imagesUrlWithId =
products.map(product => {
const productName = product.pName.split(' ')[0]
const urls = fetchApiImg(productName)
return { id: product.id, urls }
})
const imagesUrls = await Promise.all(imagesUrlWithId.map(iu => iu.urls))
imagesUrlWithId = imagesUrlWithId.map((ob, index) => {
return {
id: ob.id,
urls: imagesUrls[index]
}
}
)
return imagesUrlWithId
}
async function main () {
try {
const imagesUrls = await getImagesUrls()
await writeFileAsync('./static/mock/products-images.json', JSON.stringify(imagesUrls), { flag: 'w+' })
} catch (error) {
console.log(error)
}
}
main()
API_KEY ( 1 ).
, , 1 , 500 ( ddos).
:
await Promise.all(
products.map(async product => {
const productName = product.pName.split(' ')[0]
const imgUrl = await fetchApiImg(productName)
imagesUrl.push({ id: product.id, url: imgUrl })
})
)
, url ( ), imagesUrl () Promise. await Promise.all .
product.pName.split(' ')[0] β
imagesUrl id url .
const query = QS.stringify({ key: API_KEY, q: searchQuery, per_page: '3', image_type: 'photo' })
const resPr = got(`https://pixabay.com/api/?${query}`)
const json = await resPr.json()
if (json.hits && json.hits.length > 0 && json.hits[0].largeImageURL && json.hits[0].webformatURL) {
return {
imgXL: json.hits[0].largeImageURL,
imgL: json.hits[0].webformatURL
}
} else {
throw 'no image'
}
QS.stringify querystring ( ) ( , ?)
got(https://pixabay.com/api/?${query}
)
await resPr.json() , json
Store
, .
store/index.js
const sleep = m => new Promise(r => setTimeout(r, m))
const categories = [
{
id: 'cats',
cTitle: '',
cName: '',
cSlug: 'cats',
cMetaDescription: ' ',
cDesc: '',
cImage: 'https://source.unsplash.com/300x300/?cat,cats',
products: []
},
{
id: 'dogs',
cTitle: '',
cName: '',
cSlug: 'dogs',
cMetaDescription: ' ',
cDesc: '',
cImage: 'https://source.unsplash.com/300x300/?dog,dogs',
products: []
},
{
id: 'wolfs',
cTitle: '',
cName: '',
cSlug: 'wolfs',
cMetaDescription: ' ',
cDesc: '',
cImage: 'https://source.unsplash.com/300x300/?wolf',
products: []
},
{
id: 'bulls',
cTitle: '',
cName: '',
cSlug: 'bulls',
cMetaDescription: ' ',
cDesc: '',
cImage: 'https://source.unsplash.com/300x300/?bull',
products: []
}
]
function addProductsToCategory (products, productsImages, category) {
const categoryInner = { ...category, products: [] }
products.map(p => {
if (p.category_id === category.id) {
categoryInner.products.push({
id: p.id,
pName: p.pName,
pSlug: p.pSlug,
pPrice: p.pPrice,
image: productsImages.find(img => img.id === p.id).urls
})
}
})
return categoryInner
}
export const state = () => ({
categoriesList: [],
currentCategory: {},
currentProduct: {}
})
export const mutations = {
SET_CATEGORIES_LIST (state, categories) {
state.categoriesList = categories
},
SET_CURRENT_CATEGORY (state, category) {
state.currentCategory = category
},
SET_CURRENT_PRODUCT (state, product) {
state.currentProduct = product
}
}
export const actions = {
async getCategoriesList ({ commit }) {
try {
await sleep(1000)
await commit('SET_CATEGORIES_LIST', categories)
} catch (err) {
console.log(err)
throw new Error(' , ')
}
},
async getCurrentCategory ({ commit }, { route }) {
await sleep(1000)
const category = categories.find((cat) => cat.cSlug === route.params.CategorySlug)
const [products, productsImages] = await Promise.all(
[
this.$axios.$get('/mock/products.json'),
this.$axios.$get('/mock/products-images.json')
]
)
await commit('SET_CURRENT_CATEGORY', addProductsToCategory(products, productsImages, category))
}
}
:
const [products, productsImages] = await Promise.all(
[
this.$axios.$get('/mock/products.json'),
this.$axios.$get('/mock/products-images.json')
]
)
api. 20 .
Vue ( ).
- :

Header.vue<template>
<div :class="$style.wrapper">
<div :class="$style.header">
<n-link :class="$style.logo" to="/">
<p>
</p>
</n-link>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" module>
.wrapper {
background-color: $basic-bg-color;
height: 70px;
}
.header {
@include globalWrapper;
display: flex;
}
.logo {
font-size: 2.2em;
font-weight: 700;
opacity: 1;
color: #000;
text-decoration: none;
}
</style>
default.vue
default.vue<Header />
<nuxt />
mainWrapper .
, max-width . global-variables.scss.
@mixin globalWrapper {
max-width: 1280px;
margin: 0 auto;
padding: 0 20px;
}
$basic-bg-color: #fcc000;
. Header
.header {
@include globalWrapper;
display: flex;
}
.
:

:

_ProductSlug.vue
_ProductSlug.vue<template>
<div :class="$style.page">
<div :class="$style.topBlock">
<div :class="$style.topLeftBlock">
<a :href="product.images.imgXL" target="_blank">
<img
v-lazy="product.images.imgL"
:class="$style.image"
/>
</a>
</div>
<div :class="$style.topRightBlock">
<h1>{{ product.pName }}</h1>
<p>: {{ product.pPrice }}</p>
</div>
</div>
<h2></h2>
<p>{{ product.pDesc }}</p>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
async asyncData ({ app, params, route, error }) {
try {
await app.store.dispatch('getCurrentProduct', { route })
} catch (err) {
console.log(err)
return error({
statusCode: 404,
message: ' '
})
}
},
computed: {
...mapState({
product: 'currentProduct'
})
},
head () {
return {
title: this.product.pTitle,
meta: [
{
hid: 'description',
name: 'description',
content: this.product.pMetaDescription
}
]
}
}
}
</script>
<style lang="scss" module>
.page {
@include globalWrapper;
}
.image {
width: 400px;
height: auto;
}
.topBlock {
padding-top: 2em;
display: flex;
.topLeftBlock {
display: flex;
}
.topRightBlock {
padding-left: 2em;
display: flex;
flex-direction: column;
}
}
</style>
,
<a :href="product.images.imgXL" target="_blank">
<img
v-lazy="product.images.imgL"
:class="$style.image"
/>
</a>
imgL , imgL .

. nuxt meta , route
{
name: 'category-CategorySlug',
meta: [
{}
],
path: '/category/dogs',
hash: '',
query: {},
params: {
CategorySlug: 'dogs'
},
fullPath: '/category/dogs',
matched: [
{
path: '/category/:CategorySlug?',
regex: /^\/category(?:\/((?:[^\/]+?)))?(?:\/(?=$))?$/i,
components: [Object],
instances: {},
name: 'category-CategorySlug',
parent: undefined,
matchAs: undefined,
redirect: undefined,
beforeEnter: undefined,
meta: {},
props: {}
}
]
}
.
, , , :
Vuex :
index.js
const sleep = m => new Promise(r => setTimeout(r, m))
const categories = [
{
id: 'cats',
cTitle: '',
cName: '',
cSlug: 'cats',
cMetaDescription: ' ',
cDesc: '',
cImage: 'https://source.unsplash.com/300x300/?cat,cats',
products: []
},
{
id: 'dogs',
cTitle: '',
cName: '',
cSlug: 'dogs',
cMetaDescription: ' ',
cDesc: '',
cImage: 'https://source.unsplash.com/300x300/?dog,dogs',
products: []
},
{
id: 'wolfs',
cTitle: '',
cName: '',
cSlug: 'wolfs',
cMetaDescription: ' ',
cDesc: '',
cImage: 'https://source.unsplash.com/300x300/?wolf',
products: []
},
{
id: 'bulls',
cTitle: '',
cName: '',
cSlug: 'bulls',
cMetaDescription: ' ',
cDesc: '',
cImage: 'https://source.unsplash.com/300x300/?bull',
products: []
}
]
function getProduct (products, productsImages, productSlug) {
const innerProduct = products.find(p => p.pSlug === productSlug)
if (!innerProduct) return null
return {
...innerProduct,
images: productsImages.find(img => img.id === innerProduct.id).urls,
category: categories.find(cat => cat.id === innerProduct.category_id)
}
}
function addProductsToCategory (products, productsImages, category) {
const categoryInner = { ...category, products: [] }
products.map(p => {
if (p.category_id === category.id) {
categoryInner.products.push({
id: p.id,
pName: p.pName,
pSlug: p.pSlug,
pPrice: p.pPrice,
image: productsImages.find(img => img.id === p.id).urls
})
}
})
return categoryInner
}
function getBreadcrumbs (pageType, route, data) {
const crumbs = []
crumbs.push({
title: '',
url: '/'
})
switch (pageType) {
case 'category':
crumbs.push({
title: data.cName,
url: `/category/${data.cSlug}`
})
break
case 'product':
crumbs.push({
title: data.category.cName,
url: `/category/${data.category.cSlug}`
})
crumbs.push({
title: data.pName,
url: `/product/${data.pSlug}`
})
break
default:
break
}
return crumbs
}
export const state = () => ({
categoriesList: [],
currentCategory: {},
currentProduct: {},
bredcrumbs: []
})
export const mutations = {
SET_CATEGORIES_LIST (state, categories) {
state.categoriesList = categories
},
SET_CURRENT_CATEGORY (state, category) {
state.currentCategory = category
},
SET_CURRENT_PRODUCT (state, product) {
state.currentProduct = product
},
SET_BREADCRUMBS (state, crumbs) {
state.bredcrumbs = crumbs
},
RESET_BREADCRUMBS (state) {
state.bredcrumbs = []
}
}
export const actions = {
async setBreadcrumbs ({ commit }, data) {
await commit('SET_BREADCRUMBS', data)
},
async getCategoriesList ({ commit }) {
try {
await sleep(300)
await commit('SET_CATEGORIES_LIST', categories)
} catch (err) {
console.log(err)
throw new Error(' , ')
}
},
async getCurrentCategory ({ commit, dispatch }, { route }) {
await sleep(300)
const category = categories.find((cat) => cat.cSlug === route.params.CategorySlug)
const [products, productsImages] = await Promise.all(
[
this.$axios.$get('/mock/products.json'),
this.$axios.$get('/mock/products-images.json')
]
)
const crubms = getBreadcrumbs('category', route, category)
await dispatch('setBreadcrumbs', crubms)
await commit('SET_CURRENT_CATEGORY', addProductsToCategory(products, productsImages, category))
},
async getCurrentProduct ({ commit, dispatch }, { route }) {
await sleep(300)
const productSlug = route.params.ProductSlug
const [products, productsImages] = await Promise.all(
[
this.$axios.$get('/mock/products.json'),
this.$axios.$get('/mock/products-images.json')
]
)
const product = getProduct(products, productsImages, productSlug)
const crubms = getBreadcrumbs('product', route, product)
await dispatch('setBreadcrumbs', crubms)
await commit('SET_CURRENT_PRODUCT', product)
}
}
.
function getBreadcrumbs (pageType, route, data) {
const crumbs = []
crumbs.push({
title: '',
url: '/'
})
switch (pageType) {
case 'category':
crumbs.push({
title: data.cName,
url: `/category/${data.cSlug}`
})
break
case 'product':
crumbs.push({
title: data.category.cName,
url: `/category/${data.category.cSlug}`
})
crumbs.push({
title: data.pName,
url: `/product/${data.pSlug}`
})
break
default:
break
}
return crumbs
}
: 1 route, data ( - pageType .
.
( 2- .)
api
const crubms = getBreadcrumbs('product', route, product)
, , . .
store

, Breadcrumbs.vue
Breadcrumbs.vue<template>
<div v-if="bredcrumbs && bredcrumbs.length > 0" :class="$style.breadcrumbs">
<ul>
<li v-for="cr in bredcrumbs" :key="cr.url">
<n-link :to="cr.url">
{{ cr.title }}
</n-link>
</li>
</ul>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
bredcrumbs: 'bredcrumbs'
})
}
}
</script>
<style lang="scss" module>
/* Style the list */
.breadcrumbs {
background-color: #eee;
padding: 10px 16px;
ul {
list-style: none;
@include globalWrapper;
li {
display: inline;
font-size: 18px;
+ ::before {
padding: 8px;
color: black;
content: '/\00a0';
}
a {
color: #0275d8;
text-decoration: none;
&:hover {
color: #01447e;
text-decoration: underline;
}
}
}
}
}
</style>
layout, :


. . , .
middlware
export default async function ({ store, route }) {
if (route.matched.length > 0) {
await Promise.all(route.matched.map(async ({ name }) => {
if (name === 'index') {
await store.commit('RESET_BREADCRUMBS')
}
}))
}
}
nuxt.config.js
router: {
middleware: ['resetBreacrumbs'],
prefetchLinks: false
},
, β .
, .
api , localStorage.
, Vuex watcher . localStorage, , , , . , .
nuxt-vuex-localstorage
nuxt-vuex-localstorage nuxt.config.js
['nuxt-vuex-localstorage', {
...(isDev && {
mode: 'debug'
}),
localStorage: ['cart']
}]
localStorage ( , "" ), mode: 'debug'. Vuex cart.
Vuex
cart.js
const sleep = m => new Promise(r => setTimeout(r, m))
export const state = () => ({
products: [],
version: '0.0.1'
})
export const mutations = {
ADD_PRODUCT (state, product) {
if (!state.products.find(p => product.id === p.id)) {
state.products = [...state.products, product]
}
},
SET_PRODUCT (state, { productId, data }) {
state.products = [...state.products.filter(prod => prod.id !== productId), data]
},
REMOVE_PRODUCT (state, productId) {
state.products = Array.from(state.products.filter(prod => prod.id !== productId))
}
}
export const actions = {
async addProduct ({ commit }, data) {
await sleep(300)
await commit('ADD_PRODUCT', data)
},
async removeProduct ({ commit }, productId) {
await sleep(300)
await commit('REMOVE_PRODUCT', productId)
}
}
version: '0.0.1' storage .
, .
BuyButton.vue<template>
<div v-if="product">
<button
v-if="!isProductAdded"
:class="$style.buy"
@click.prevent="buyClickHandler"
>
</button>
<a
v-else
:class="$style.added"
href="#"
@click.prevent="addedClickHandler"
>
</a>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex'
export default {
props: {
product: {
type: Object,
required: true
}
},
computed: {
...mapState({
products: state => state.cart.products
}),
isProductAdded () {
return this.products.find(p => p.id === this.product.id)
}
},
methods: {
...mapActions({
addProduct: 'cart/addProduct',
removeProduct: 'cart/removeProduct'
}),
buyClickHandler () {
this.addProduct(this.product)
},
addedClickHandler () {
this.removeProduct(this.product.id)
}
}
}
</script>
<style lang="scss" module>
.buy {
background-color: $basic-bg-color; /* Green */
border: none;
color: #000;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
&:hover {
cursor: pointer;
}
}
.added {
text-decoration: none;
border-bottom: 2px dotted;
}
</style>
.
- mapActions cart/addProduct
- mapState , , state state => state.cart.products
- isProductAdded
- , , , .
.


, ( ).
Nuxt.
, , .
, .
, , 6 . .
, , , .
!