404 start

This commit is contained in:
Kseninia Mikhaylova 2024-06-19 13:28:02 +03:00
parent c708cccf43
commit 4e499163de
8 changed files with 267 additions and 176 deletions

162
app.vue
View File

@ -1,167 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
const config = useRuntimeConfig()
const apiBase = config.public.apiBase
console.log(config)
import { marked } from 'marked';
import k_logo from '@/assets/icons/logo.svg'
import og_img from '/og_img.png'
import '@/assets/main.scss' import '@/assets/main.scss'
const { data: seoData } = await useFetch<ApiKpType>(`${apiBase}/kp/1`)
useSeoMeta({
title: seoData.value?.title,
ogTitle: seoData.value?.title,
description: seoData.value?.content,
ogDescription: seoData.value?.content,
ogImage: config.public.baseUrl + og_img,
// twitterCard: 'summary_large_image',
})
const { data: pagesData } = await useFetch<ApiPagesType[]>(`${apiBase}/pages/?ordering=order`)
const { data: reviewsData } = await useFetch<ApiReviewsType[]>(`${apiBase}/review/`)
const { data: calculatorData } = await useFetch(`${apiBase}/calculator/5/`)
const about = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'about')
const reviews = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'clients')
const delivery = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'delivery')
const advantages = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'advantages')
const roubleSign = new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB',
});
const aboutText = computed(() => marked.parse(about?.content || ''))
const deliveryText = computed(() => delivery?.content.split('[col]').map(el => marked.parse(el || '')))
const advantagesText = computed(() => {
let c = advantages?.content || ''
Object.entries(calculatorData.value || {}).map(item => {
c = c.replaceAll(`[${item[0]}]`, roubleSign.format(item[1]))
})
return marked.parse(c)
})
const { data: footerData } = await useFetch<ApiFooterType[]>(`${apiBase}/footer/?ordering=small_text`)
const { data: social_networkData } = await useFetch<ApiSocial_networkType[]>(`${apiBase}/social_network/`)
const { data: advData } = await useFetch<ApiAdvantageType[]>(`${apiBase}/advantage/`)
const { scrollToAnchor, scrollToTop } = useAnchorScroll({
toTop: {
scrollOptions: {
behavior: 'smooth',
offsetTop: 0,
}
},
})
</script> </script>
<template> <template>
<div> <div>
<div class="header"> <Header />
<div class="container"> <NuxtPage />
<div class="logo"> <Footer />
Купи забор
</div>
<div class="menu">
<template v-for="item in pagesData">
<NuxtLink :to="item.external_link || `#${item.slug}`" :target="item.external_link ? '_blank' : '_self'"
@click="scrollToAnchor(item.slug)">
{{ item.menu_title }}
</NuxtLink>
</template>
</div>
</div>
</div>
<div class="siteblock bg-white" :id="about?.slug" v-if="about">
<div class="container">
<h1 class="siteblock-title">{{ about?.title }}</h1>
<div class="siteblock-content">
<div class="siteblock-text">
<span v-html="aboutText"></span>
</div>
</div>
<div class="siteblock-image">
<NuxtImg :src="[apiBase, about?.image].join('/')" alt="разные цвета забора" title=""/>
</div>
<div class="feature">
<template v-for="item in advData">
<div class="feature-item">
<div class="feature-name">{{ item.title }}</div> {{ item.content }}
</div>
</template>
</div>
</div>
</div>
<div class="siteblock siteblock_imgbg bg-slate-500"
:style="[{ backgroundImage: `url(${[apiBase, reviews?.image].join('/')})` }]" v-if="reviews">
<NuxtImg :src="[apiBase, reviews?.image].join('/')" class="invisible" alt="отзыв" title=""/>
</div>
<div class="siteblock bg-white" :id="reviews?.slug" v-if="reviews">
<div class="container">
<template v-for="item in reviewsData?.slice(0, 3)">
<div class="review">
<div class="review-image">
<NuxtImg :src="[apiBase, item.image].join('/')" :alt="item.text" title=""/>
</div>
<div class="review-content">
{{ item.comment }}
</div>
<div class="review-title">
{{ item.text }}
</div>
</div>
</template>
</div>
</div>
<div class="siteblock bg-white">
<div class="container gap-4">
<div class="col-span-full xl:col-span-8">
<ExpDiagram />
</div>
<div class="col-span-full xl:col-span-4 prose">
<span v-html="advantagesText"></span>
</div>
</div>
</div>
<div class="siteblock siteblock_imgbg bg-slate-500"
:style="[{ backgroundImage: `url(${[apiBase, delivery?.image].join('/')})` }]">
<NuxtImg :src="[apiBase, delivery?.image].join('/')" class="invisible" v-if="delivery" alt="коричневый забор" title=""/>
</div>
<div class="siteblock siteblock_calc bg-white">
<CalcValues />
<Suspense>
<CalcModels />
</Suspense>
</div>
<div class="siteblock bg-white" :id="delivery?.slug" v-if="deliveryText">
<div class="container">
<div class="prose col-span-12 xl:col-span-6">
<span v-html="deliveryText[0]"></span>
</div>
<div class="prose col-span-12 xl:col-span-6">
<span v-html="deliveryText[1]"></span>
</div>
</div>
</div>
<div class="footer" id="contacts">
<div class="container">
<div class="k-logo">
<k_logo />
</div>
<template v-for="item in footerData">
<div class="footer-text" :class="[{ 'footer-text-small': item.small_text }]">{{ item.text }}</div>
</template>
<div class="footer-text footer-text-social" v-if="social_networkData">
<template v-for="item in social_networkData">
<a :href="item.link" target="_blank">
<Icon :name="item.icon" /> {{ item.name }}
</a>
</template>
</div>
</div>
</div>
<Modal /> <Modal />
</div> </div>
</template> </template>

View File

@ -1,12 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { AdditiveBlending, CanvasTexture, FrontSide, RepeatWrapping } from 'three'; import { CanvasTexture, FrontSide, RepeatWrapping } from 'three';
import { TresCanvas, useTexture } from '@tresjs/core' import { TresCanvas, useTexture } from '@tresjs/core'
import { OrbitControls, Environment, useGLTF } from '@tresjs/cientos' import { OrbitControls, useGLTF } from '@tresjs/cientos'
const section_count = useState<number>('section_count') const lamelles_count = use_lamelles_count()
const fence_section = useState<number>('fence_section') const fence_section = use_fence_section()
const extra_section = useState<number>('extra_section') const remove_pillar = use_remove_pillar()
const max_size = useState<number>('max_size') const pillar_color = use_pillar_color()
const lamelle_color = use_lamelle_color()
const section_count = use_section_count()
const extra_section = use_extra_section()
const total_length = use_total_length()
const min_length = use_min_length()
const controlsState = reactive({ const controlsState = reactive({
distance: section_count.value, distance: section_count.value,

View File

@ -2,18 +2,17 @@
import { getColorNameFromRal } from '@/components/ral' import { getColorNameFromRal } from '@/components/ral'
import type { ralTypes } from '@/components/ral' import type { ralTypes } from '@/components/ral'
const predefPillarColors = ['3004', '7043', '6028', '5013', '8016', '1020', '3005', '4009'] import { predefLamelleColors, predefPillarColors } from '~/composables/useCalc';
const predefLamelleColors = ['3009', '9003', '6027', '5024', '9001', '1012', '3007', '4007']
const lamelles_count = useState('lamelles_count', () => 14) const lamelles_count = use_lamelles_count()
const fence_section = useState<number>('fence_section', () => 1000 * 0.001) const fence_section = use_fence_section()
const remove_pillar = useState<boolean>('remove_pillar', () => false) const remove_pillar = use_remove_pillar()
const pillar_color = useState<ralTypes>('pillar_color', () => predefPillarColors[Math.floor(Math.random() * predefPillarColors.length)] as ralTypes) const pillar_color = use_pillar_color()
const lamelle_color = useState<ralTypes>('lamelle_color', () => predefLamelleColors[Math.floor(Math.random() * predefLamelleColors.length)] as ralTypes) const lamelle_color = use_lamelle_color()
const section_count = useState('section_count', () => 5) const section_count = use_section_count()
const extra_section = useState('extra_section', () => 0) const extra_section = use_extra_section()
const total_length = useState('total_length', () => (1000 * 5 - 100) * 0.001) const total_length = use_total_length()
const min_length = useState('min_length', () => 1000) const min_length = use_min_length()
const parametric = reactive({ const parametric = reactive({
length: { length: {

27
components/footer.vue Normal file
View File

@ -0,0 +1,27 @@
<script setup lang="ts">
const config = useRuntimeConfig()
const apiBase = config.public.apiBase
import k_logo from '@/assets/icons/logo.svg'
const { data: footerData } = await useFetch<ApiFooterType[]>(`${apiBase}/footer/?ordering=small_text`)
const { data: social_networkData } = await useFetch<ApiSocial_networkType[]>(`${apiBase}/social_network/`)
</script>
<template>
<div class="footer" id="contacts">
<div class="container">
<div class="k-logo">
<k_logo />
</div>
<template v-for="item in footerData">
<div class="footer-text" :class="[{ 'footer-text-small': item.small_text }]">{{ item.text }}</div>
</template>
<div class="footer-text footer-text-social" v-if="social_networkData">
<template v-for="item in social_networkData">
<a :href="item.link" target="_blank">
<Icon :name="item.icon" /> {{ item.name }}
</a>
</template>
</div>
</div>
</div>
</template>

33
components/header.vue Normal file
View File

@ -0,0 +1,33 @@
<script setup lang="ts">
const config = useRuntimeConfig()
const apiBase = config.public.apiBase
const { data: pagesData } = await useFetch<ApiPagesType[]>(`${apiBase}/pages/?ordering=order`)
const { scrollToAnchor, scrollToTop } = useAnchorScroll({
toTop: {
scrollOptions: {
behavior: 'smooth',
offsetTop: 0,
}
},
})
const route = useRoute()
console.log(route)
</script>
<template>
<div class="header">
<div class="container">
<div class="logo">
Купи забор
</div>
<div class="menu">
<template v-for="item in pagesData">
<NuxtLink :to="item.external_link || `#${item.slug}`"
:target="item.external_link ? '_blank' : '_self'" @click="scrollToAnchor(item.slug)">
{{ item.menu_title }}
</NuxtLink>
</template>
</div>
</div>
</div>
</template>

14
composables/useCalc.ts Normal file
View File

@ -0,0 +1,14 @@
import type { ralTypes } from '@/components/ral'
export const predefPillarColors = ['3004', '7043', '6028', '5013', '8016', '1020', '3005', '4009']
export const predefLamelleColors = ['3009', '9003', '6027', '5024', '9001', '1012', '3007', '4007']
export const use_lamelles_count = () => useState('lamelles_count', () => 14)
export const use_fence_section = () => useState<number>('fence_section', () => 1000 * 0.001)
export const use_remove_pillar = () => useState<boolean>('remove_pillar', () => false)
export const use_pillar_color = () => useState<ralTypes>('pillar_color', () => predefPillarColors[Math.floor(Math.random() * predefPillarColors.length)] as ralTypes)
export const use_lamelle_color = () => useState<ralTypes>('lamelle_color', () => predefLamelleColors[Math.floor(Math.random() * predefLamelleColors.length)] as ralTypes)
export const use_section_count = () => useState('section_count', () => 5)
export const use_extra_section = () => useState('extra_section', () => 0)
export const use_total_length = () => useState('total_length', () => (1000 * 5 - 100) * 0.001)
export const use_min_length = () => useState('min_length', () => 1000)

47
error.vue Normal file
View File

@ -0,0 +1,47 @@
<script setup lang="ts">
const config = useRuntimeConfig()
const apiBase = config.public.apiBase
import '@/assets/main.scss'
import type { NuxtError } from '#app'
import og_img from '/og_img.png'
const { data: seoData } = await useFetch<ApiKpType>(`${apiBase}/kp/1`)
useSeoMeta({
title: seoData.value?.title,
ogTitle: seoData.value?.title,
description: seoData.value?.content,
ogDescription: seoData.value?.content,
ogImage: config.public.baseUrl + og_img,
// twitterCard: 'summary_large_image',
})
const props = defineProps({
error: Object as () => NuxtError
})
const handleError = () => clearError({ redirect: '/' })
</script>
<template>
<div>
<Header />
<div class="siteblock bg-white">
<div class="container prose">
<div class="col-span-full">
<h1>Похоже, произошла ошибка</h1>
<p>{{ props.error }}</p>
<button @click="navigateTo('/')" class="not-prose">На главную</button>
</div>
</div>
</div>
<div class="siteblock siteblock_calc bg-white">
<Suspense>
<CalcModels />
</Suspense>
</div>
<Footer />
<Modal />
</div>
</template>

122
pages/index.vue Normal file
View File

@ -0,0 +1,122 @@
<script setup lang="ts">
const config = useRuntimeConfig()
const apiBase = config.public.apiBase
import { marked } from 'marked';
import og_img from '/og_img.png'
const { data: seoData } = await useFetch<ApiKpType>(`${apiBase}/kp/1`)
useSeoMeta({
title: seoData.value?.title,
ogTitle: seoData.value?.title,
description: seoData.value?.content,
ogDescription: seoData.value?.content,
ogImage: config.public.baseUrl + og_img,
// twitterCard: 'summary_large_image',
})
const { data: pagesData } = await useFetch<ApiPagesType[]>(`${apiBase}/pages/?ordering=order`)
const { data: reviewsData } = await useFetch<ApiReviewsType[]>(`${apiBase}/review/`)
const { data: calculatorData } = await useFetch(`${apiBase}/calculator/5/`)
const about = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'about')
const reviews = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'clients')
const delivery = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'delivery')
const advantages = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'advantages')
const roubleSign = new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB',
});
const aboutText = computed(() => marked.parse(about?.content || ''))
const deliveryText = computed(() => delivery?.content.split('[col]').map(el => marked.parse(el || '')))
const advantagesText = computed(() => {
let c = advantages?.content || ''
Object.entries(calculatorData.value || {}).map(item => {
c = c.replaceAll(`[${item[0]}]`, roubleSign.format(item[1]))
})
return marked.parse(c)
})
const { data: advData } = await useFetch<ApiAdvantageType[]>(`${apiBase}/advantage/`)
</script>
<template>
<div>
<div class="siteblock bg-white" :id="about?.slug" v-if="about">
<div class="container">
<h1 class="siteblock-title">{{ about?.title }}</h1>
<div class="siteblock-content">
<div class="siteblock-text">
<span v-html="aboutText"></span>
</div>
</div>
<div class="siteblock-image">
<NuxtImg :src="[apiBase, about?.image].join('/')" alt="разные цвета забора" title="" />
</div>
<div class="feature">
<template v-for="item in advData">
<div class="feature-item">
<div class="feature-name">{{ item.title }}</div> {{ item.content }}
</div>
</template>
</div>
</div>
</div>
<div class="siteblock siteblock_imgbg bg-slate-500"
:style="[{ backgroundImage: `url(${[apiBase, reviews?.image].join('/')})` }]" v-if="reviews">
<NuxtImg :src="[apiBase, reviews?.image].join('/')" class="invisible" alt="отзыв" title="" />
</div>
<div class="siteblock bg-white" :id="reviews?.slug" v-if="reviews">
<div class="container">
<template v-for="item in reviewsData?.slice(0, 3)">
<div class="review">
<div class="review-image">
<NuxtImg :src="[apiBase, item.image].join('/')" :alt="item.text" title="" />
</div>
<div class="review-content">
{{ item.comment }}
</div>
<div class="review-title">
{{ item.text }}
</div>
</div>
</template>
</div>
</div>
<div class="siteblock bg-white">
<div class="container gap-4">
<div class="col-span-full xl:col-span-8">
<ExpDiagram />
</div>
<div class="col-span-full xl:col-span-4 prose">
<span v-html="advantagesText"></span>
</div>
</div>
</div>
<div class="siteblock siteblock_imgbg bg-slate-500"
:style="[{ backgroundImage: `url(${[apiBase, delivery?.image].join('/')})` }]">
<NuxtImg :src="[apiBase, delivery?.image].join('/')" class="invisible" v-if="delivery"
alt="коричневый забор" title="" />
</div>
<div class="siteblock siteblock_calc bg-white">
<CalcValues />
<Suspense>
<CalcModels />
</Suspense>
</div>
<div class="siteblock bg-white" :id="delivery?.slug" v-if="deliveryText">
<div class="container">
<div class="prose col-span-12 xl:col-span-6">
<span v-html="deliveryText[0]"></span>
</div>
<div class="prose col-span-12 xl:col-span-6">
<span v-html="deliveryText[1]"></span>
</div>
</div>
</div>
</div>
</template>