dev #26

Merged
ksenia_mikhailova merged 39 commits from dev into main 2024-07-15 09:00:49 +03:00
9 changed files with 123 additions and 21 deletions
Showing only changes of commit ef336a5b2b - Show all commits

View File

@ -174,6 +174,9 @@ label {
input { input {
@apply bg-neutral-200 border border-gray-300 text-gray-900 rounded focus:ring-blue-500 focus:border-blue-500 text-lg p-2.5 disabled:cursor-not-allowed; @apply bg-neutral-200 border border-gray-300 text-gray-900 rounded focus:ring-blue-500 focus:border-blue-500 text-lg p-2.5 disabled:cursor-not-allowed;
} }
input[type=checkbox] {
@apply w-4 h-4;
}
textarea { textarea {
@apply block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 max-w-full min-h-10 max-h-40; @apply block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 max-w-full min-h-10 max-h-40;

View File

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { apiFetch } from '~/utils/apiFetch'; import { apiFetch } from '~/utils/apiFetch';
const { data: pagesData } = await apiFetch<ApiPagesType[]>(`pages/?ordering=order`) const { data: menuData } = await apiFetch<ApiMenuType>(`menu/1/`)
const pagesData = (menuData.value ? menuData.value.pages : []).sort((a, b) => a.order - b.order)
const route = useRoute() const route = useRoute()
</script> </script>
<template> <template>

View File

@ -30,12 +30,15 @@ type modalDataType = {
phone?: string phone?: string
name?: string name?: string
email?: string email?: string
policy: boolean
} }
const modal_data = reactive<modalDataType>({ const modal_data = reactive<modalDataType>({
email: undefined, email: undefined,
phone: undefined, phone: undefined,
name: undefined name: undefined,
policy: true
}) })
const modal_form = reactive({ const modal_form = reactive({
disabled: true, disabled: true,
errors: [], errors: [],
@ -62,13 +65,17 @@ const validate = () => {
modal_form.disabled = true modal_form.disabled = true
if ( if (
(modal_data.phone && phone_regexp.test(modal_data.phone)) (
|| (modal_data.email && email_regex.test(modal_data.email)) (modal_data.phone && phone_regexp.test(modal_data.phone))
|| (modal_data.email && email_regex.test(modal_data.email))
) && modal_data.policy == true
) { ) {
modal_form.disabled = false modal_form.disabled = false
return return
} }
} }
watch(modal_data, validate)
const submit = async (e: any) => { const submit = async (e: any) => {
goal('submit_form', modal_data) goal('submit_form', modal_data)
modal_state.show_status = 'loading' modal_state.show_status = 'loading'
@ -178,6 +185,14 @@ const modalStatus = {
'success': ["mdi:check-circle-outline", 'Данные отправлены'], 'success': ["mdi:check-circle-outline", 'Данные отправлены'],
'error': ["mdi:close-circle-outline", 'Ошибка отправки'], 'error': ["mdi:close-circle-outline", 'Ошибка отправки'],
} }
const policy = () => {
navigateTo('/policy', {
open: {
target: "_blank",
},
});
}
</script> </script>
<template> <template>
<div v-if="isModalOpen" class="modal-backdrop" @click.self="toggleModal"> <div v-if="isModalOpen" class="modal-backdrop" @click.self="toggleModal">
@ -194,11 +209,17 @@ const modalStatus = {
<template v-else-if="modal_state.show_form"> <template v-else-if="modal_state.show_form">
<h2>Оставьте контакты для связи </h2> <h2>Оставьте контакты для связи </h2>
<form @submit.prevent="submit" ref="form"> <form @submit.prevent="submit" ref="form">
<input type="text" placeholder="Ваше имя" v-model="modal_data.name" @keyup="validate" /> <input type="text" placeholder="Ваше имя" v-model="modal_data.name" />
<input type="phone" placeholder="Ваш номер телефона" v-model="modal_data.phone" <input type="phone" placeholder="Ваш номер телефона" v-model="modal_data.phone"
@keypress="validateInput" @keyup="validate" /> @keypress="validateInput" />
<input type="e-mail" placeholder="Ваш e-mail" v-model="modal_data.email" @keypress="validateInput" <input type="e-mail" placeholder="Ваш e-mail" v-model="modal_data.email"
@keyup="validate" /> @keypress="validateInput" />
<div class="flex gap-4 justify-between items-center">
<input type="checkbox" id="policy" v-model="modal_data.policy" />
<label for="policy">Нажимая кнопку "Отправить" Вы также даете согласие на <NuxtLink to="policy" @click="policy">обработку персональных
данных</NuxtLink>.
</label>
</div>
{{ total_txt && total_txt.total[0] }} {{ total_txt && total_txt.total[0] }}
<div class="flex gap-4"> <div class="flex gap-4">
<button class="not-prose" :disabled="modal_form.disabled" type="submit">Отправить</button> <button class="not-prose" :disabled="modal_form.disabled" type="submit">Отправить</button>

View File

@ -1,13 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
const config = useRuntimeConfig()
const apiBase = config.public.apiBase
import '@/assets/main.scss' import '@/assets/main.scss'
const config = useRuntimeConfig()
import { apiFetch } from './utils/apiFetch'; import { apiFetch } from './utils/apiFetch';
import type { NuxtError } from '#app' import type { NuxtError } from '#app'
import og_img from '/og_img.png' import og_img from '/og_img.png'
const { data: seoData } = await apiFetch<ApiKpType>(`kp/2`) const { data: seoData } = await apiFetch<ApiKpType>(`kp/2/`)
useSeoMeta({ useSeoMeta({
title: seoData.value?.title, title: seoData.value?.title,
ogTitle: seoData.value?.title, ogTitle: seoData.value?.title,
@ -20,9 +18,11 @@ useSeoMeta({
const props = defineProps({ const props = defineProps({
error: Object as () => NuxtError error: Object as () => NuxtError
}) })
const route = useRoute()
if(route.path !== '/404') {
// navigateTo('/404')
}
</script> </script>
<template> <template>
<div> <div>
<Header /> <Header />
@ -32,6 +32,11 @@ const props = defineProps({
<h1>Вы ищете страницу, которой не существует. Вернитесь на главную страницу сайта</h1> <h1>Вы ищете страницу, которой не существует. Вернитесь на главную страницу сайта</h1>
<p>Извините, но мы не можем найти запрашиваемую страницу. К сожалению, мы не можем помочь вам с <p>Извините, но мы не можем найти запрашиваемую страницу. К сожалению, мы не можем помочь вам с
покупкой забора здесь.</p> покупкой забора здесь.</p>
<p class="hidden">
<code>
{{ error?.message }}
</code>
</p>
<button @click="navigateTo('/')" class="not-prose">Вернуться на главную</button> <button @click="navigateTo('/')" class="not-prose">Вернуться на главную</button>
</div> </div>
</div> </div>

View File

@ -42,6 +42,11 @@ export default defineNuxtConfig({
}, },
}, },
}, },
vue: {
compilerOptions: {
isCustomElement: (tag) => ['nobr'].includes(tag),
}
},
ssr: true, ssr: true,
modules: [ modules: [
'@nuxtjs/tailwindcss', '@nuxtjs/tailwindcss',

50
pages/[slug].vue Normal file
View File

@ -0,0 +1,50 @@
<script setup lang="ts">
const config = useRuntimeConfig()
const imgBase = config.public.imgBase
import { apiFetch } from '~/utils/apiFetch';
import { marked } from 'marked';
import og_img from '/og_img.png'
const { data: seoData } = await apiFetch<ApiKpType>(`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 route = useRoute()
const { data } = await apiFetch<ApiPagesType>(`pages/${route.params.slug}/`)
if (!data.value) {
throw createError({
statusCode: 404,
})
}
const policyText = computed(() => {
if (!data?.value) return ''
let c = data?.value.content || ''
return marked.parse(c)
})
</script>
<template>
<div>
<div class="siteblock bg-white">
<div class="container">
<h1 class="siteblock-title">{{ data?.title || '404' }}</h1>
<div class="col-span-full prose max-w-full" v-html="policyText" />
</div>
</div>
<div class="siteblock-image" v-if="data?.image">
<NuxtImg :src="[imgBase, data?.image].join('/')" :alt="data.title || 'фоновая картинка'" title="" format="webp" />
</div>
<div class="siteblock siteblock_calc bg-white">
<Suspense>
<CalcModels />
</Suspense>
</div>
</div>
</template>

View File

@ -7,7 +7,7 @@ import { marked } from 'marked';
import og_img from '/og_img.png' import og_img from '/og_img.png'
const { data: seoData } = await apiFetch<ApiKpType>(`kp/1`) const { data: seoData } = await apiFetch<ApiKpType>(`kp/1/`)
useSeoMeta({ useSeoMeta({
title: seoData.value?.title, title: seoData.value?.title,
ogTitle: seoData.value?.title, ogTitle: seoData.value?.title,
@ -17,15 +17,16 @@ useSeoMeta({
// twitterCard: 'summary_large_image', // twitterCard: 'summary_large_image',
}) })
const { data: pagesData } = await apiFetch<ApiPagesType[]>(`pages/?ordering=order`) const { data: menuData } = await apiFetch<ApiMenuType>(`menu/1/?ordering=order`)
const pagesData = menuData.value ? menuData.value.pages : []
const { data: reviewsData } = await apiFetch<ApiReviewsType[]>(`review/`) const { data: reviewsData } = await apiFetch<ApiReviewsType[]>(`review/`)
const { data: calculatorData } = await apiFetch(`calculator/5/`) const { data: calculatorData } = await apiFetch(`calculator/5/`)
const about = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'about') const about = pagesData.find(el => el.slug == 'about')
const reviews = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'clients') const reviews = pagesData.find(el => el.slug == 'clients')
const delivery = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'how_to') const delivery = pagesData.find(el => el.slug == 'how_to')
const advantages = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'advantages') const advantages = pagesData.find(el => el.slug == 'advantages')
const roubleSign = new Intl.NumberFormat('ru-RU', { const roubleSign = new Intl.NumberFormat('ru-RU', {
style: 'currency', style: 'currency',

7
types/index.d.ts vendored
View File

@ -14,8 +14,15 @@ type ApiKpType = {
is_indexed: boolean is_indexed: boolean
} }
type ApiMenuType = {
id: number
type: string
pages: ApiPagesType[]
}
type ApiPagesType = { type ApiPagesType = {
id: number id: number
order: number
title: string title: string
menu_title: string menu_title: string
slug: string slug: string

View File

@ -5,6 +5,15 @@ export async function apiFetch<T>(path: string) {
headers.set('Referer', config.public.baseUrl) headers.set('Referer', config.public.baseUrl)
return useFetch<T>(`${apiBase}/${path}`, { return useFetch<T>(`${apiBase}/${path}`, {
baseURL: config.public.baseUrl, baseURL: config.public.baseUrl,
headers headers,
onResponseError({ response }) {
console.log(response.status)
console.log(response.url)
window.location.pathname = '/404'
throw createError({
statusCode: 404,
fatal: true
})
},
}) })
} }