dev #90

Merged
ksenia_mikhailova merged 20 commits from dev into main 2025-03-20 14:15:19 +03:00
28 changed files with 432 additions and 177 deletions
Showing only changes of commit 21ae73f0f6 - Show all commits

View File

@ -6,6 +6,5 @@ import '@/assets/main.scss'
<Header /> <Header />
<NuxtPage /> <NuxtPage />
<Footer /> <Footer />
<Modal />
</div> </div>
</template> </template>

View File

@ -27,21 +27,18 @@ const cameraStat = reactive({
</div> </div>
</template> </template>
<Loader /> <Loader />
<TresCanvas clear-color="#e2e8f0"> <Scene :canvasProps="{ clearColor: '#e2e8f0' }">
<TresPerspectiveCamera v-bind="cameraStat" ref="camera" /> <TresPerspectiveCamera v-bind="cameraStat" ref="camera" />
<OrbitControls v-bind="controlsState" make-default /> <OrbitControls v-bind="controlsState" make-default />
<Suspense> <Suspense>
<ModelSmoothCamera /> <ModelSmoothCamera />
</Suspense> </Suspense>
<Suspense>
<ModelEnv />
</Suspense>
<TresGroup> <TresGroup>
<Suspense> <Suspense>
<ModelParametric /> <ModelParametric />
</Suspense> </Suspense>
</TresGroup> </TresGroup>
</TresCanvas> </Scene>
</ClientOnly> </ClientOnly>
</div> </div>
</template> </template>

View File

@ -19,6 +19,7 @@ const section_count = use_section_count()
const extra_section = use_extra_section() const extra_section = use_extra_section()
const total_length = use_total_length() const total_length = use_total_length()
const min_length = use_min_length() const min_length = use_min_length()
const globalFenceType = useGlobalFenceType()
if (!pillar_color.value) { if (!pillar_color.value) {
const r = Math.floor(Math.random() * predefPillarColors.length) const r = Math.floor(Math.random() * predefPillarColors.length)
@ -205,7 +206,8 @@ const calc_table = computed(() => {
:goto_cam="new Vector3(1, 2, -1)" /> :goto_cam="new Vector3(1, 2, -1)" />
<label for="pillar_topper">Колпак столба</label> <label for="pillar_topper">Колпак столба</label>
<DropdownPicker type="topper" :cb="setPillarTopper" name="pillar_topper" <DropdownPicker type="topper" :cb="setPillarTopper" name="pillar_topper"
:pattern="getTopper(pillar_topper)" :disabled="remove_pillar" :pattern="getTopper(pillar_topper, globalFenceType?.type == 'aristo' ? 'aristoToppers' : 'baseToppers')"
:disabled="remove_pillar"
:goto_target="new Vector3(fence_section * -0.5, lamelles_count * lamelle_height, 0)" :goto_target="new Vector3(fence_section * -0.5, lamelles_count * lamelle_height, 0)"
:goto_cam="new Vector3(-1, 2, 1)" /> :goto_cam="new Vector3(-1, 2, 1)" />
</div> </div>

View File

@ -1,24 +1,39 @@
<script setup lang="ts"> <script setup lang="ts">
import { getFilename as getPattern, patterns } from '../pattern'; import { getFilename as getPattern, patterns } from '../pattern';
import { getFilename as getTopper, toppers } from '../topper'; import { getFilename as getTopper, baseToppers, aristoToppers } from '../topper';
const globalFenceType = useGlobalFenceType()
const props = defineProps(['cb', 'patterns']); const props = defineProps(['cb', 'patterns']);
type pType = { type pType = {
list: typeof patterns | typeof toppers, list: typeof patterns | typeof baseToppers,
func: any func: any
type: string | null
} }
const p = { const p = reactive({
list: [], list: [],
type: null,
func: () => { } func: () => { }
} as pType } as pType)
if (props.patterns == 'pattern') { if (props.patterns == 'pattern') {
p.list = patterns; p.list = patterns;
p.func = getPattern p.func = getPattern
} else if (props.patterns == 'topper') { } else if (props.patterns == 'topper') {
p.list = toppers p.list = baseToppers
p.func = getTopper p.func = getTopper
} }
watch(globalFenceType, () => {
if (props.patterns == 'topper') {
if (globalFenceType.value?.type == 'standart') {
p.list = baseToppers
p.type = 'toppers'
}
if (globalFenceType.value?.type == 'aristo') {
p.list = aristoToppers
p.type = 'aristoToppers'
}
}
})
</script> </script>
<template> <template>
<template v-for="item in p.list"> <template v-for="item in p.list">

View File

@ -17,22 +17,6 @@ const explosion_state = use_explosion_state()
const toggleExpState = () => { const toggleExpState = () => {
explosion_state.value = !explosion_state.value explosion_state.value = !explosion_state.value
} }
const back_light = ref()
const secondary_light = ref()
const loadAll = async () => {
const { scene: back } = await useGLTF('/models_light/back_exp.glb')
const { scene: secondary } = await useGLTF('/models_light/secondary_exp.glb')
const k = 0.03
back_light.value = back.children[0]
back_light.value.intensity = back_light.value.intensity * k
back_light.value.shadow.bias = -0.02
secondary_light.value = secondary.children[0]
secondary_light.value.intensity = secondary_light.value.intensity * k
secondary_light.value.shadow.bias = -0.01
}
const changeDistance = (v = 1) => { const changeDistance = (v = 1) => {
if (camera.value && controls.value) { if (camera.value && controls.value) {
@ -41,9 +25,6 @@ const changeDistance = (v = 1) => {
camera.value.position.normalize().multiplyScalar(r) camera.value.position.normalize().multiplyScalar(r)
} }
} }
onMounted(() => {
loadAll()
})
</script> </script>
<template> <template>
<div class="h-96 relative"> <div class="h-96 relative">
@ -53,14 +34,11 @@ onMounted(() => {
Загрузка 3D модели Загрузка 3D модели
</div> </div>
</template> </template>
<TresCanvas height="600"> <Scene :canvasProps="{ height: '600' }">
<TresPerspectiveCamera :position="[-7, 2, 4]" ref="camera" /> <TresPerspectiveCamera :position="[-7, 2, 4]" ref="camera" />
<OrbitControls v-bind="controlsState" ref="controls" make-default /> <OrbitControls v-bind="controlsState" ref="controls" make-default />
<ModelEnv />
<Suspense>
<ModelDiagram /> <ModelDiagram />
</Suspense> </Scene>
</TresCanvas>
</ClientOnly> </ClientOnly>
<div class="canvas-icons"> <div class="canvas-icons">
<a href="#" @click.prevent="toggleExpState"> <a href="#" @click.prevent="toggleExpState">

View File

@ -7,7 +7,7 @@ import { getName as getTopperName } from './topper';
const config = useRuntimeConfig() const config = useRuntimeConfig()
const apiBase = config.public.apiBase const apiBase = config.public.apiBase
const { data: calculatorData } = await apiFetch<ApiCalcType>(`calculator/5/`) const props = defineProps(['calcData'])
const isModalOpen = useState('modal_open', () => false) const isModalOpen = useState('modal_open', () => false)
const lamelle_height = useState<number>('lamelle_height') const lamelle_height = useState<number>('lamelle_height')
@ -122,12 +122,14 @@ const total_colors = computed(() => {
`Столбы ${pillar_color.value} ${getColorNameFromRal(pillar_color.value)}`, `Столбы ${pillar_color.value} ${getColorNameFromRal(pillar_color.value)}`,
] ]
}) })
const total_txt = computed(() => { const total_txt = computed(() => {
if (!calculatorData.value) return const calculatorData = props.calcData as ApiCalcType
const pillar = parseFloat(calculatorData.value.pillar) if (!calculatorData) return
const pillar_base = parseFloat(calculatorData.value.pillar_base) const pillar = parseFloat(calculatorData.pillar)
const lamelles_block = parseFloat(calculatorData.value.lamelles_block) const pillar_base = parseFloat(calculatorData.pillar_base)
const { discount } = calculatorData.value const lamelles_block = parseFloat(calculatorData.lamelles_block)
const { discount } = calculatorData
const sections = section_count.value as number const sections = section_count.value as number
const extra_m = extra_section.value as number * 0.001 const extra_m = extra_section.value as number * 0.001

View File

@ -4,6 +4,7 @@ import { useGLTF } from '@tresjs/cientos'
import { getColorHexFromRal } from '../ral'; import { getColorHexFromRal } from '../ral';
const explosion_state = use_explosion_state() const explosion_state = use_explosion_state()
const globalFenceType = useGlobalFenceType()
const k = 1.5 const k = 1.5
const targetExplosion = { const targetExplosion = {
@ -14,24 +15,67 @@ const targetExplosion = {
stolb: [0 * k, 0 * k, 1 * k], stolb: [0 * k, 0 * k, 1 * k],
verh: [0 * k, 0.25 * k, 0 * k], verh: [0 * k, 0.25 * k, 0 * k],
} }
const { scene: standart_kosynka } = await useGLTF('/models_exp/kosynka.glb')
const { scene: standart_krepleniye_planok } = await useGLTF('/models_exp/krepleniye_planok.glb')
const { scene: standart_osnova_stolba } = await useGLTF('/models_exp/osnova_stolba.glb')
const { scene: standart_planki } = await useGLTF('/models_exp/planki.glb')
const { scene: standart_stolb } = await useGLTF('/models_exp/stolb.glb')
const { scene: standart_verh } = await useGLTF('/models_exp/verh.glb')
const { scene: aristo_kosynka } = await useGLTF('/models_aristo_exp/kosynka.glb')
const { scene: aristo_krepleniye_planok } = await useGLTF('/models_aristo_exp/krepleniye_planok.glb')
const { scene: aristo_osnova_stolba } = await useGLTF('/models_aristo_exp/osnova_stolba.glb')
const { scene: aristo_planki } = await useGLTF('/models_aristo_exp/planki.glb')
const { scene: aristo_stolb } = await useGLTF('/models_aristo_exp/stolb.glb')
const { scene: aristo_verh } = await useGLTF('/models_aristo_exp/verh.glb')
const kosynka = ref()
const krepleniye_planok = ref()
const osnova_stolba = ref()
const planki = ref()
const stolb = ref()
const verh = ref()
const setModels = () => {
const t = globalFenceType.value?.type || 'aristo'
if (t == 'aristo') {
kosynka.value = aristo_kosynka
krepleniye_planok.value = aristo_krepleniye_planok
osnova_stolba.value = aristo_osnova_stolba
planki.value = aristo_planki
stolb.value = aristo_stolb
verh.value = aristo_verh
}
if (t == "standart") {
kosynka.value = standart_kosynka
krepleniye_planok.value = standart_krepleniye_planok
osnova_stolba.value = standart_osnova_stolba
planki.value = standart_planki
stolb.value = standart_stolb
verh.value = standart_verh
}
}
setModels()
watch(() => globalFenceType.value?.type, (t) => {
setModels()
set_material(planki.value, getColorHexFromRal(lamelle_color.value));
[stolb, verh, krepleniye_planok].map(el => set_material(el.value, getColorHexFromRal(pillar_color.value)))
})
const { scene: kosynka } = await useGLTF('/models_exp/kosynka.glb')
const { scene: krepleniye_planok } = await useGLTF('/models_exp/krepleniye_planok.glb')
const { scene: osnova_stolba } = await useGLTF('/models_exp/osnova_stolba.glb')
const { scene: planki } = await useGLTF('/models_exp/planki.glb')
const { scene: stolb } = await useGLTF('/models_exp/stolb.glb')
const { scene: verh } = await useGLTF('/models_exp/verh.glb')
const lamelle_color = use_lamelle_color() const lamelle_color = use_lamelle_color()
const pillar_color = use_pillar_color() const pillar_color = use_pillar_color()
set_material(planki, getColorHexFromRal(lamelle_color.value)); set_material(planki.value, getColorHexFromRal(lamelle_color.value));
[stolb, verh, krepleniye_planok].map(el => set_material(el, getColorHexFromRal(pillar_color.value))) [stolb, verh, krepleniye_planok].map(el => set_material(el.value, getColorHexFromRal(pillar_color.value)))
</script> </script>
<template> <template>
<Suspense> <Suspense>
<TresGroup :position-y="-3.5" :scale="2"> <TresGroup :position-y="!globalFenceType || globalFenceType.type == 'aristo' ? -2 : -3.5" :scale="2"
:key="globalFenceType?.type">
<ModelItem :model="kosynka" :target="explosion_state ? targetExplosion.kosynka : [0, 0, 0]" /> <ModelItem :model="kosynka" :target="explosion_state ? targetExplosion.kosynka : [0, 0, 0]" />
<ModelItem :model="krepleniye_planok" <ModelItem :model="krepleniye_planok"
:target="explosion_state ? targetExplosion.krepleniye_planok : [0, 0, 0]" /> :target="explosion_state ? targetExplosion.krepleniye_planok : [0, 0, 0]" />

View File

@ -32,7 +32,7 @@ onMounted(async () => {
const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null; const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null;
scene.value.environment = newEnvMap scene.value.environment = newEnvMap
scene.value.environmentIntensity = 1 scene.value.environmentIntensity = 1.25
scene.value.environmentRotation.z = 0.25 scene.value.environmentRotation.z = 0.25
result.renderTarget.texture.dispose(); result.renderTarget.texture.dispose();
}) })

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { BufferGeometry, Matrix4, Mesh, Object3D, Vector3 } from 'three'; import { BufferGeometry, Group, Matrix4, Mesh, Object3D, Vector3 } from 'three';
import { getColorHexFromRal } from '../ral'; import { getColorHexFromRal } from '../ral';
const props = defineProps(['index', 'models', 'last_element', 'first_element']) const props = defineProps(['index', 'models', 'last_element', 'first_element'])
@ -16,7 +16,6 @@ const pillar_pattern = use_pattern()
const pillar_topper = use_topper() const pillar_topper = use_topper()
const lamelle_color = use_lamelle_color() const lamelle_color = use_lamelle_color()
const lSize = lamelle_height.value
const pillar_size = 104 * 0.001 const pillar_size = 104 * 0.001
const pillar_one_pos = ref() const pillar_one_pos = ref()
@ -80,7 +79,7 @@ const instanced_fixing_two_el = [
const lamelleMatrix = (i: number) => { const lamelleMatrix = (i: number) => {
const scale_x = (((extra.value as number) || fence_section.value) * 9.9) const scale_x = (((extra.value as number) || fence_section.value) * 9.9)
const pos_x = pillar_size * 0.5 const pos_x = pillar_size * 0.5
const pos_y = (lSize * i) const pos_y = (lamelle_height.value * i)
const pos_z = 0.022 * scale_koef const pos_z = 0.022 * scale_koef
return new Matrix4().fromArray([ return new Matrix4().fromArray([
scale_x, 0, 0, 0, scale_x, 0, 0, 0,
@ -91,7 +90,7 @@ const lamelleMatrix = (i: number) => {
} }
const fixingOneMatrix = (i: number) => { const fixingOneMatrix = (i: number) => {
const pos_x = pillar_one_pos.value + pillar_size * 0.66 const pos_x = pillar_one_pos.value + pillar_size * 0.66
const pos_y = (lSize * i) + 0.01 * scale_koef; const pos_y = (lamelle_height.value * i) + 0.01 * scale_koef;
const pos_z = 0.022 * scale_koef const pos_z = 0.022 * scale_koef
return new Matrix4().fromArray([ return new Matrix4().fromArray([
1, 0, 0, 0, 1, 0, 0, 0,
@ -102,7 +101,7 @@ const fixingOneMatrix = (i: number) => {
} }
const fixingTwoMatrix = (i: number) => { const fixingTwoMatrix = (i: number) => {
const pos_x = pillar_two_pos.value - pillar_size * 0.66 const pos_x = pillar_two_pos.value - pillar_size * 0.66
const pos_y = (lSize * i) + 0.01 * scale_koef; const pos_y = (lamelle_height.value * i) + 0.01 * scale_koef;
const pos_z = 0.022 * scale_koef const pos_z = 0.022 * scale_koef
return new Matrix4().fromArray([ return new Matrix4().fromArray([
1, 0, 0, 0, 1, 0, 0, 0,
@ -130,19 +129,35 @@ const setBraceCount = () => {
setBraceCount() setBraceCount()
watch(lamelles_count, setBraceCount) watch(lamelles_count, setBraceCount)
const calculateAddScale = (model: Mesh, k = 1): number => {
if (!model.geometry.boundingBox) {
model.geometry.computeBoundingBox(); // Вычисляем boundingBox, если он не был вычислен ранее
}
const boundingBoxHeight = Math.abs(
model.geometry.boundingBox!.max.y - model.geometry.boundingBox!.min.y
);
if (boundingBoxHeight === 0) {
console.warn('Bounding box height is zero. Returning default scale of 1.');
return 1; // Возвращаем 1 при делении на ноль
}
return (lamelle_height.value * k) / boundingBoxHeight;
};
const pillar = ref<Mesh[]>([]) const pillar = ref<Mesh[]>([])
const setPillar = () => { const setPillar = () => {
const top = props.models.pillar_top.children[0]; const top = props.models.pillar_top.children[0];
top.position.setComponent(1, lSize * lamelles_count.value + lamelles_count.value * 0.0001 * scale_koef); top.position.setComponent(1, lamelle_height.value * lamelles_count.value * scale_koef);
const pillar_outer = props.models.pillar_center.children[0]; const pillar_outer = props.models.pillar_center.children[0];
pillar_outer.scale.setComponent(1, lamelles_count.value); pillar_outer.scale.setComponent(1, lamelles_count.value * calculateAddScale(pillar_outer));
const pillar_inner = props.models.pillar_inner.children[0]; const pillar_inner = props.models.pillar_inner.children[0];
pillar_inner.scale.setComponent(1, lamelles_count.value); pillar_inner.scale.setComponent(1, lamelles_count.value * calculateAddScale(pillar_inner));
const bottom = props.models.pillar_bottom.children[0]; const bottom = props.models.pillar_bottom.children[0];
bottom.position.setComponent(1, lSize * -0.5);
let arr = [top, pillar_outer, pillar_inner, bottom] let arr = [top, pillar_outer, pillar_inner, bottom]
arr.map(el => { arr.map(el => {
@ -151,48 +166,50 @@ const setPillar = () => {
set_material( set_material(
{ children: [arr[2]] }, { children: [arr[2]] },
getColorHexFromRal(pillar_color.value), getColorHexFromRal(pillar_color.value),
{ pattern: pillar_pattern.value, count: lamelles_count.value }, { pattern: pillar_pattern.value, count: lamelles_count.value * calculateAddScale(pillar_inner) },
true true
) )
pillar.value = arr.map(el => el.clone()) pillar.value = arr.map(el => el.clone())
} }
setPillar() setPillar()
watch([pillar_pattern, pillar_color, pillar_topper, fence_section, lamelles_count], setPillar) watch([pillar_pattern, pillar_color, pillar_topper, fence_section, lamelles_count, lamelle_height], setPillar)
const fastening = ref<Object3D[]>([]) const fastening = ref<Object3D[]>([])
const setFastening = () => { const setFastening = () => {
const top_one = props.models.fixing.clone().children[0]; const top_one = props.models.fixing.clone().children[0];
top_one.position.set( top_one.position.set(
pillar_one_pos.value + pillar_size * 0.66, pillar_one_pos.value + pillar_size * 0.66,
lamelles_count.value * lSize - 0.015 * scale_koef, lamelles_count.value * lamelle_height.value - 0.015 * scale_koef,
0.025 * scale_koef 0.025 * scale_koef
) )
const top_two = props.models.fixing.clone().children[0]; const top_two = props.models.fixing.clone().children[0];
top_two.position.set( top_two.position.set(
pillar_two_pos.value - pillar_size * 0.66, pillar_two_pos.value - pillar_size * 0.66,
lamelles_count.value * lSize - 0.01 * scale_koef, lamelles_count.value * lamelle_height.value - 0.01 * scale_koef,
0.025 * scale_koef 0.025 * scale_koef
) )
const v = ((extra.value as number) || fence_section.value) * 10 const v = ((extra.value as number) || fence_section.value) * 10
const top = props.models.fastening_top.clone().children[0]; const top = props.models.fastening_top.clone().children[0];
top.position.set( top.position.set(
pillar_size * 0.5, pillar_size * 0.5,
lamelles_count.value * lSize - 0.0275 * scale_koef, lamelles_count.value * lamelle_height.value -0.0275 * scale_koef,
0 0
); );
top.scale.setComponent(0, v); top.scale.setComponent(0, v);
let c = 0.0019 * scale_koef
const side_one = props.models.fastening_side.clone().children[0]; const side_one = props.models.fastening_side.clone().children[0];
side_one.name = 'side_one' side_one.name = 'side_one'
side_one.position.set(pillar_one_pos.value, 0, 0.002 * scale_koef); side_one.position.set(pillar_one_pos.value, 0, c);
side_one.scale.set(1, lamelles_count.value, 1) side_one.scale.set(1, lamelles_count.value * calculateAddScale(side_one), 1)
const side_two = props.models.fastening_side.clone().children[0]; const side_two = props.models.fastening_side.clone().children[0];
side_two.name = 'side_two' side_two.name = 'side_two'
side_two.scale.set(-1, lamelles_count.value, -1) side_two.scale.set(-1, lamelles_count.value * calculateAddScale(side_two), -1)
side_two.position.set(pillar_two_pos.value, 0, -0.005 * scale_koef); side_two.position.set(pillar_two_pos.value, 0, c);
let arr = [top_one, top_two, top, side_one, side_two]; let arr = [top_one, top_two, top, side_one, side_two];
[top, side_one, side_two, ...braces.value].map(el => { [top, side_one, side_two, ...braces.value].map(el => {
@ -201,7 +218,7 @@ const setFastening = () => {
fastening.value = arr.map(el => el.clone()) fastening.value = arr.map(el => el.clone())
} }
setFastening() setFastening()
watch([pillar_color, lamelles_count, pillar_one_pos, pillar_two_pos], setFastening) watch([pillar_color, pillar_one_pos, pillar_two_pos, lamelles_count, lamelle_height], setFastening)
const setLamellesColor = () => { const setLamellesColor = () => {
if (instanced_lamelle.value) { if (instanced_lamelle.value) {
@ -214,6 +231,7 @@ watch([instanced_lamelle, lamelle_color], setLamellesColor)
watch([ watch([
instanced_lamelle, instanced_lamelle,
lamelles_count, lamelles_count,
lamelle_height,
fence_section, fence_section,
], () => { ], () => {
const translationVector = new Vector3(0, 20, 20) const translationVector = new Vector3(0, 20, 20)

View File

@ -3,7 +3,7 @@ import { Object3D, Vector3 } from 'three';
//@ts-ignore //@ts-ignore
import { useGLTF, } from '@tresjs/cientos' import { useGLTF, } from '@tresjs/cientos'
import type { OrbitControlsProps } from '@tresjs/cientos/dist/core/controls/OrbitControls.vue.js'; import type { OrbitControlsProps } from '@tresjs/cientos/dist/core/controls/OrbitControls.vue.js';
import { getModel, toppers, type toppersIds } from '../topper'; import { getModel, allToppers, baseToppers, aristoToppers, type toppersIds } from '../topper';
const section_count = use_section_count() const section_count = use_section_count()
const extra_section = use_extra_section() const extra_section = use_extra_section()
@ -20,10 +20,36 @@ const goto_target = use_goto_target()
const { scene, controls, camera } = useTresContext() const { scene, controls, camera } = useTresContext()
const { seek, seekAll } = useSeek() const { seek, seekAll } = useSeek()
const topper_models = {} as { [key: toppersIds]: Object3D } const globalFenceType = useGlobalFenceType()
const top = ref(null)
const pillar_top = ref()
const pillar_center = ref(null)
const pillar_bottom = ref(null)
const pillar_inner = ref(null)
const pillar_brace = ref(null)
const fastening_top = ref(null)
const fastening_side = ref(null)
const fixing = ref(null)
const lamelle = ref(null)
const total = ref(0)
const size = ref(0)
const count = ref(0)
const min_for_square = 12;
const loadAll = async () => {
const topper_models = {
baseToppers: {} as { [key: toppersIds]: Object3D },
aristoToppers: {} as { [key: toppersIds]: Object3D },
}
for (const key of Object.keys(allToppers)) {
let toppers = allToppers[key]
for await (const element of toppers) { for await (const element of toppers) {
const { scene } = await useGLTF(getModel(element.id)) const { scene } = await useGLTF(getModel(element.id, key))
topper_models[element.id] = scene topper_models[key][element.id] = scene
}
} }
const { scene: model_pillar_center } = await useGLTF('/models_one/pillar/center.glb') const { scene: model_pillar_center } = await useGLTF('/models_one/pillar/center.glb')
@ -36,26 +62,34 @@ const { scene: model_fastening_side } = await useGLTF('/models_one/fastening/sid
const { scene: model_fixing } = await useGLTF('/models_one/fixing.glb'); const { scene: model_fixing } = await useGLTF('/models_one/fixing.glb');
const { scene: top_model } = await useGLTF('/models_one/top_100.glb', { draco: true }) const { scene: top_model } = await useGLTF('/models_one/top_100.glb', { draco: true })
const { scene: lamelle_model } = await useGLTF('/models_one/lamel_100.glb', { draco: true }); // const { scene: lamelle_model } = await useGLTF('/models_one/lamel_100.glb', { draco: true });
const top = ref(top_model) const { scene: lamelle_model_standart } = await useGLTF('/models_one/lamel_100.glb', { draco: true });
const pillar_top = ref(topper_models[pillar_topper.value]) const { scene: lamelle_model_aristo } = await useGLTF('/models_one/lamel_100_aristo.glb', { draco: true });
const pillar_center = ref(model_pillar_center)
const pillar_bottom = ref(model_pillar_bottom)
const pillar_inner = ref(model_pillar_inner)
const pillar_brace = ref(model_pillar_brace)
const fastening_top = ref(model_fastening_top)
const fastening_side = ref(model_fastening_side)
const fixing = ref(model_fixing)
const lamelle = ref(lamelle_model)
watch(pillar_topper, async () => { top.value = top_model
pillar_top.value = topper_models[pillar_topper.value] pillar_center.value = model_pillar_center
}) pillar_bottom.value = model_pillar_bottom
pillar_inner.value = model_pillar_inner
pillar_brace.value = model_pillar_brace
fastening_top.value = model_fastening_top
fastening_side.value = model_fastening_side
fixing.value = model_fixing
lamelle.value = lamelle_model_standart
const total = ref((section_count.value + ~~(!!extra_section.value))) const setPillarTopper = () => {
const size = ref(Math.ceil(total.value / 4)) let key = 'baseToppers'
const count = ref((total.value >= 4) ? size.value : total.value) if (globalFenceType.value?.type == 'aristo') {
key = 'aristoToppers'
}
pillar_top.value = topper_models[key][pillar_topper.value]
}
setPillarTopper()
watch(pillar_topper, setPillarTopper)
total.value = (section_count.value + ~~(!!extra_section.value))
size.value = Math.ceil(total.value / 4)
count.value = (total.value >= 4) ? size.value : total.value
watch(() => [section_count.value, extra_section.value], () => { watch(() => [section_count.value, extra_section.value], () => {
total.value = (section_count.value + ~~(!!extra_section.value)) total.value = (section_count.value + ~~(!!extra_section.value))
@ -100,7 +134,7 @@ const setTarget = (smooth = false) => {
goto_cam.value = new Vector3(f, f, f) goto_cam.value = new Vector3(f, f, f)
} }
} }
watch([lamelles_count, fence_section], () => { watch([lamelles_count, fence_section, lamelle_height], () => {
setTarget() setTarget()
open_calc.value = [] open_calc.value = []
}) })
@ -110,13 +144,32 @@ watch(open_calc, () => {
setTarget(true) setTarget(true)
} }
}) })
const min_for_square = 12;
setTarget() setTarget()
const setLamelleType = () => {
if (globalFenceType.value?.type == 'standart') {
lamelle.value = lamelle_model_standart
lamelle_height.value = 0.115
pillar_topper.value = 0
setPillarTopper()
}
if (globalFenceType.value?.type == 'aristo') {
lamelle.value = lamelle_model_aristo
lamelle_height.value = 0.196
pillar_topper.value = 0
setPillarTopper()
}
}
setLamelleType()
watch(() => globalFenceType.value?.id, setLamelleType)
}
onMounted(async () => { await loadAll() })
</script> </script>
<template> <template>
<TresGroup name="base"> <TresGroup name="base">
<template v-for="line in (total >= min_for_square) ? 4 : 1" :key="`${line}_${count}`"> <template v-for="line in (total >= min_for_square) ? 4 : 1"
:key="`${globalFenceType?.id ?? 5}_${line}_${count}`">
<ModelLine :models="{ <ModelLine :models="{
top, top,
pillar_center, pillar_top, pillar_bottom, pillar_inner, pillar_center, pillar_top, pillar_bottom, pillar_inner,

55
components/scene.vue Normal file
View File

@ -0,0 +1,55 @@
<script setup>
const props = defineProps({
canvasProps: {
type: Object,
default: () => ({}),
},
cameraProps: {
type: Object,
default: () => ({}),
}
});
const container = ref(null);
const isIntersecting = ref(true);
let observer;
// Вычисляем renderMode на основе видимости
const renderMode = computed(() => (isIntersecting.value ? 'always' : 'never'));
const startObserver = () => {
observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
isIntersecting.value = entry.isIntersecting;
});
},
{ threshold: 0.1 } // Настройте порог видимости по вашему усмотрению
);
if (container.value) {
observer.observe(container.value);
}
}
onMounted(async () => {
await nextTick(); // Ждём завершения рендеринга
startObserver(); // Запускаем IntersectionObserver
});
onBeforeUnmount(() => {
if (observer) {
observer.disconnect();
}
});
</script>
<template>
<div ref="container" class="h-full">
<Suspense>
<TresCanvas v-bind="canvasProps" :render-mode="renderMode" :key="renderMode">
<ModelEnv />
<Suspense>
<slot />
</Suspense>
</TresCanvas>
</Suspense>
</div>
</template>

View File

@ -1,24 +1,63 @@
export const toppers = [ interface Topper {
id: number; // ID элемента
name: string; // Название
filename: string; // Имя файла
model: string; // Модель
}
export const baseToppers = [
{ name: 'Ровный', filename: 'icon_stolb_verh_3.svg', model: 'top' }, { name: 'Ровный', filename: 'icon_stolb_verh_3.svg', model: 'top' },
{ name: 'Вершина 1', filename: 'icon_stolb_verh_2.svg', model: 'decor1' }, { name: 'Вершина 1', filename: 'icon_stolb_verh_2.svg', model: 'decor1' },
{ name: 'Вершина 2', filename: 'icon_stolb_verh_1.svg', model: 'decor2' }, { name: 'Вершина 2', filename: 'icon_stolb_verh_1.svg', model: 'decor2' },
].map((el, i) => Object.assign(el, { id: i })) ].map((el, i) => Object.assign(el, { id: i }))
export const getFilename = (id: toppersIds) => { export const aristoToppers = [
const el = toppers.find(el => el.id == id) { name: 'Колпак', filename: 'icon_stolb_verh_1.svg', model: 'aristo1' },
].map((el, i) => Object.assign(el, { id: i }))
export const allToppers = {
'baseToppers': baseToppers,
'aristoToppers': aristoToppers,
} as { [key: string]: Topper[] }
function getToppersByType(type: string | null): any {
if (!type) type = 'NONE'
// Проверяем, существует ли указанный тип в объекте allToppers
if (allToppers[type]) {
return allToppers[type];
}
// Если типа нет, берем первый доступный элемент из allToppers
const keys = Object.keys(allToppers);
if (keys.length > 0) {
const firstKey = keys[0];
return allToppers[firstKey];
}
// Если объект allToppers пустой, возвращаем значение по умолчанию (например, пустой массив)
return [];
}
export const getFilename = (id: toppersIds, type: string | null) => {
let toppers = getToppersByType(type)
const el = toppers.find((el: Topper) => el.id == id)
if (!el || !el.filename) return undefined if (!el || !el.filename) return undefined
return `/topper/${el?.filename}` return `/topper/${el?.filename}`
} }
export const getName = (id: toppersIds) => { export const getName = (id: toppersIds, type: string | null) => {
const el = toppers.find(el => el.id == id) let toppers = getToppersByType(type)
const el = toppers.find((el: Topper) => el.id == id)
if (!el || !el.filename) return undefined if (!el || !el.filename) return undefined
return el.name return el.name
} }
export const getModel = (id: toppersIds) => { export const getModel = (id: toppersIds, type: string | null) => {
const el = toppers.find(el => el.id == id) let toppers = getToppersByType(type)
let el = toppers.find((el: Topper) => el.id == id)
if (!el || !el.filename) return undefined if (!el || !el.filename) return undefined
return `/models_one/pillar/topper/${el?.model}.glb` return `/models_one/pillar/topper/${el?.model}.glb`
} }
export type toppersIds = typeof toppers[number]['id'] export type toppersIds = typeof allToppers[string][number]['id']

View File

@ -0,0 +1,7 @@
interface calc {
id: number
type: 'standart' | 'aristo'
calc: ApiCalcType
}
export const useGlobalFenceType = () => useState<calc | null>('fence-global-type', () => null)

View File

@ -32,7 +32,7 @@ if(route.path !== '/404') {
<h1>Вы ищете страницу, которой не существует. Вернитесь на главную страницу сайта</h1> <h1>Вы ищете страницу, которой не существует. Вернитесь на главную страницу сайта</h1>
<p>Извините, но мы не можем найти запрашиваемую страницу. К сожалению, мы не можем помочь вам с <p>Извините, но мы не можем найти запрашиваемую страницу. К сожалению, мы не можем помочь вам с
покупкой забора здесь.</p> покупкой забора здесь.</p>
<p class="hidden"> <p>
<code> <code>
{{ error?.message }} {{ error?.message }}
</code> </code>

View File

@ -10,6 +10,9 @@ export default defineNuxtConfig({
htmlAttrs: { htmlAttrs: {
lang: 'ru', lang: 'ru',
}, },
link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/tabler--fence-filled.svg' },
]
}, },
}, },
ssr: true, ssr: true,
@ -25,7 +28,9 @@ export default defineNuxtConfig({
], ],
runtimeConfig: { runtimeConfig: {
public: { public: {
apiBase: 'https://mns.kustarshina.ru/kp', // apiBase: process.env.mode == 'DEVELOPMENT' ? "http://localhost:8000" : "https://mns.kustarshina.ru/kp",
apiBase: process.env.mode == 'DEVELOPMENT' ? "http://mns.dev.kustarshina.ru" : "https://mns.kustarshina.ru/kp",
// apiBase: 'http://localhost:8000',
imgBase: 'https://mns.kustarshina.ru', imgBase: 'https://mns.kustarshina.ru',
baseUrl: '', baseUrl: '',
yandexMetrika: { yandexMetrika: {

View File

@ -7,6 +7,8 @@ import { marked } from 'marked';
import og_img from '/og_img.png' import og_img from '/og_img.png'
const globalFenceType = useGlobalFenceType()
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,
@ -21,7 +23,10 @@ const { data: menuData } = await apiFetch<ApiMenuType>(`menu/1/?ordering=order`)
const pagesData = menuData.value ? menuData.value.pages : [] 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: calculators } = await apiFetch<ApiCalcType[]>('calculator/')
const openTab = ref<number>()
if (calculators.value?.length) openTab.value = calculators.value[0].id
const about = pagesData.find(el => el.slug == 'about') const about = pagesData.find(el => el.slug == 'about')
const reviews = pagesData.find(el => el.slug == 'clients') const reviews = pagesData.find(el => el.slug == 'clients')
@ -37,16 +42,32 @@ const aboutText = computed(() => marked.parse(about?.content || ''))
const deliveryText = computed(() => delivery?.content.split('[col]').map(el => marked.parse(el || ''))) const deliveryText = computed(() => delivery?.content.split('[col]').map(el => marked.parse(el || '')))
const advantagesText = computed(() => { const advantagesText = computed(() => {
let c = advantages?.content || '' let c = advantages?.content || ''
Object.entries(calculatorData.value || {}).map(item => { Object.entries(calculators.value?.find(el => el.id == openTab.value) || {}).map(item => {
c = c.replaceAll(`[${item[0]}]`, roubleSign.format(item[1])) c = c.replaceAll(`[${item[0]}]`, roubleSign.format(item[1] as number))
}) })
return marked.parse(c) return marked.parse(c)
}) })
const { data: advData } = await apiFetch<ApiAdvantageType[]>(`advantage/`) const { data: advData } = await apiFetch<ApiAdvantageType[]>(`advantage/`)
watch(openTab, () => {
if (!openTab.value) return
if (!calculators.value) return
let type = 'standart'
if (openTab.value == 6) {
type = 'aristo'
}
const v = {
id: openTab.value as number,
type: type as "standart" | "aristo",
calc: calculators.value.find(el => el.id == openTab.value) as ApiCalcType,
}
globalFenceType.value = v
})
</script> </script>
<template> <template>
<div> <div>
<Modal :calcData="(calculators || []).find(el => el.id == openTab)" />
<div class="siteblock bg-white" :id="about?.slug" v-if="about"> <div class="siteblock bg-white" :id="about?.slug" v-if="about">
<div class="container"> <div class="container">
<h1 class="siteblock-title">{{ about?.title }}</h1> <h1 class="siteblock-title">{{ about?.title }}</h1>
@ -92,6 +113,24 @@ const { data: advData } = await apiFetch<ApiAdvantageType[]>(`advantage/`)
</div> </div>
</div> </div>
<div class="siteblock bg-white" :id="advantages?.slug"> <div class="siteblock bg-white" :id="advantages?.slug">
<div class="flex col-span-full gap-2 justify-center">
<template v-for="item in calculators">
<div class="flex items-center gap-2">
<label class="rounded p-2 cursor-pointer flex gap-2" :for="item.id.toString()">
<input type="radio" :id="item.id.toString()" :value="item.id" v-model="openTab"
:checked="openTab == item.id">
<span :class="[
'text-2xl font-bold bg-clip-text text-transparent drop-shadow-xs',
openTab == item.id
? 'bg-gradient-to-r from-yellow-400 via-orange-500 to-red-600'
: 'bg-gradient-to-r from-slate-400 to-slate-600'
]">
{{ item.title }}
</span>
</label>
</div>
</template>
</div>
<div class="container gap-4"> <div class="container gap-4">
<div class="col-span-full xl:col-span-8"> <div class="col-span-full xl:col-span-8">
<ExpDiagram /> <ExpDiagram />

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/models_exp.zip Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M19 17v3a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1v-3zm-8 0v3a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-3zm9-5a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zM8.707 3.293l2 2A1 1 0 0 1 11 6v5H5V6a1 1 0 0 1 .293-.707l2-2a1 1 0 0 1 1.414 0m8 0l2 2A1 1 0 0 1 19 6v5h-6V6a1 1 0 0 1 .293-.707l2-2a1 1 0 0 1 1.414 0"/></svg>

After

Width:  |  Height:  |  Size: 424 B

View File

@ -3,6 +3,7 @@ export async function apiFetch<T>(path: string) {
const apiBase = config.public.apiBase const apiBase = config.public.apiBase
const headers = new Headers() const headers = new Headers()
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,