bx-2376-new_type #86
1
app.vue
1
app.vue
|
@ -6,6 +6,5 @@ import '@/assets/main.scss'
|
|||
<Header />
|
||||
<NuxtPage />
|
||||
<Footer />
|
||||
<Modal />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -27,21 +27,18 @@ const cameraStat = reactive({
|
|||
</div>
|
||||
</template>
|
||||
<Loader />
|
||||
<TresCanvas clear-color="#e2e8f0">
|
||||
<Scene :canvasProps="{ clearColor: '#e2e8f0' }">
|
||||
<TresPerspectiveCamera v-bind="cameraStat" ref="camera" />
|
||||
<OrbitControls v-bind="controlsState" make-default />
|
||||
<Suspense>
|
||||
<ModelSmoothCamera />
|
||||
</Suspense>
|
||||
<Suspense>
|
||||
<ModelEnv />
|
||||
</Suspense>
|
||||
<TresGroup>
|
||||
<Suspense>
|
||||
<ModelParametric />
|
||||
</Suspense>
|
||||
</TresGroup>
|
||||
</TresCanvas>
|
||||
</Scene>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
|
@ -19,6 +19,7 @@ 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 globalFenceType = useGlobalFenceType()
|
||||
|
||||
if (!pillar_color.value) {
|
||||
const r = Math.floor(Math.random() * predefPillarColors.length)
|
||||
|
@ -205,7 +206,8 @@ const calc_table = computed(() => {
|
|||
:goto_cam="new Vector3(1, 2, -1)" />
|
||||
<label for="pillar_topper">Колпак столба</label>
|
||||
<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_cam="new Vector3(-1, 2, 1)" />
|
||||
</div>
|
||||
|
|
|
@ -1,24 +1,39 @@
|
|||
<script setup lang="ts">
|
||||
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']);
|
||||
type pType = {
|
||||
list: typeof patterns | typeof toppers,
|
||||
list: typeof patterns | typeof baseToppers,
|
||||
func: any
|
||||
type: string | null
|
||||
}
|
||||
const p = {
|
||||
const p = reactive({
|
||||
list: [],
|
||||
type: null,
|
||||
func: () => { }
|
||||
} as pType
|
||||
} as pType)
|
||||
|
||||
if (props.patterns == 'pattern') {
|
||||
p.list = patterns;
|
||||
p.func = getPattern
|
||||
} else if (props.patterns == 'topper') {
|
||||
p.list = toppers
|
||||
p.list = baseToppers
|
||||
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>
|
||||
<template>
|
||||
<template v-for="item in p.list">
|
||||
|
|
|
@ -17,22 +17,6 @@ const explosion_state = use_explosion_state()
|
|||
const toggleExpState = () => {
|
||||
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) => {
|
||||
if (camera.value && controls.value) {
|
||||
|
@ -41,9 +25,6 @@ const changeDistance = (v = 1) => {
|
|||
camera.value.position.normalize().multiplyScalar(r)
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
loadAll()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="h-96 relative">
|
||||
|
@ -53,14 +34,11 @@ onMounted(() => {
|
|||
Загрузка 3D модели
|
||||
</div>
|
||||
</template>
|
||||
<TresCanvas height="600">
|
||||
<Scene :canvasProps="{ height: '600' }">
|
||||
<TresPerspectiveCamera :position="[-7, 2, 4]" ref="camera" />
|
||||
<OrbitControls v-bind="controlsState" ref="controls" make-default />
|
||||
<ModelEnv />
|
||||
<Suspense>
|
||||
<ModelDiagram />
|
||||
</Suspense>
|
||||
</TresCanvas>
|
||||
<ModelDiagram />
|
||||
</Scene>
|
||||
</ClientOnly>
|
||||
<div class="canvas-icons">
|
||||
<a href="#" @click.prevent="toggleExpState">
|
||||
|
|
|
@ -7,7 +7,7 @@ import { getName as getTopperName } from './topper';
|
|||
|
||||
const config = useRuntimeConfig()
|
||||
const apiBase = config.public.apiBase
|
||||
const { data: calculatorData } = await apiFetch<ApiCalcType>(`calculator/5/`)
|
||||
const props = defineProps(['calcData'])
|
||||
const isModalOpen = useState('modal_open', () => false)
|
||||
|
||||
const lamelle_height = useState<number>('lamelle_height')
|
||||
|
@ -122,12 +122,14 @@ const total_colors = computed(() => {
|
|||
`Столбы ${pillar_color.value} ${getColorNameFromRal(pillar_color.value)}`,
|
||||
]
|
||||
})
|
||||
|
||||
const total_txt = computed(() => {
|
||||
if (!calculatorData.value) return
|
||||
const pillar = parseFloat(calculatorData.value.pillar)
|
||||
const pillar_base = parseFloat(calculatorData.value.pillar_base)
|
||||
const lamelles_block = parseFloat(calculatorData.value.lamelles_block)
|
||||
const { discount } = calculatorData.value
|
||||
const calculatorData = props.calcData as ApiCalcType
|
||||
if (!calculatorData) return
|
||||
const pillar = parseFloat(calculatorData.pillar)
|
||||
const pillar_base = parseFloat(calculatorData.pillar_base)
|
||||
const lamelles_block = parseFloat(calculatorData.lamelles_block)
|
||||
const { discount } = calculatorData
|
||||
|
||||
const sections = section_count.value as number
|
||||
const extra_m = extra_section.value as number * 0.001
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useGLTF } from '@tresjs/cientos'
|
|||
import { getColorHexFromRal } from '../ral';
|
||||
|
||||
const explosion_state = use_explosion_state()
|
||||
const globalFenceType = useGlobalFenceType()
|
||||
|
||||
const k = 1.5
|
||||
const targetExplosion = {
|
||||
|
@ -14,24 +15,67 @@ const targetExplosion = {
|
|||
stolb: [0 * k, 0 * k, 1 * 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 pillar_color = use_pillar_color()
|
||||
|
||||
set_material(planki, getColorHexFromRal(lamelle_color.value));
|
||||
[stolb, verh, krepleniye_planok].map(el => set_material(el, getColorHexFromRal(pillar_color.value)))
|
||||
set_material(planki.value, getColorHexFromRal(lamelle_color.value));
|
||||
[stolb, verh, krepleniye_planok].map(el => set_material(el.value, getColorHexFromRal(pillar_color.value)))
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<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="krepleniye_planok"
|
||||
:target="explosion_state ? targetExplosion.krepleniye_planok : [0, 0, 0]" />
|
||||
|
|
|
@ -32,7 +32,7 @@ onMounted(async () => {
|
|||
const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null;
|
||||
|
||||
scene.value.environment = newEnvMap
|
||||
scene.value.environmentIntensity = 1
|
||||
scene.value.environmentIntensity = 1.25
|
||||
scene.value.environmentRotation.z = 0.25
|
||||
result.renderTarget.texture.dispose();
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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';
|
||||
|
||||
const props = defineProps(['index', 'models', 'last_element', 'first_element'])
|
||||
|
@ -16,7 +16,6 @@ const pillar_pattern = use_pattern()
|
|||
const pillar_topper = use_topper()
|
||||
const lamelle_color = use_lamelle_color()
|
||||
|
||||
const lSize = lamelle_height.value
|
||||
const pillar_size = 104 * 0.001
|
||||
|
||||
const pillar_one_pos = ref()
|
||||
|
@ -80,7 +79,7 @@ const instanced_fixing_two_el = [
|
|||
const lamelleMatrix = (i: number) => {
|
||||
const scale_x = (((extra.value as number) || fence_section.value) * 9.9)
|
||||
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
|
||||
return new Matrix4().fromArray([
|
||||
scale_x, 0, 0, 0,
|
||||
|
@ -91,7 +90,7 @@ const lamelleMatrix = (i: number) => {
|
|||
}
|
||||
const fixingOneMatrix = (i: number) => {
|
||||
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
|
||||
return new Matrix4().fromArray([
|
||||
1, 0, 0, 0,
|
||||
|
@ -102,7 +101,7 @@ const fixingOneMatrix = (i: number) => {
|
|||
}
|
||||
const fixingTwoMatrix = (i: number) => {
|
||||
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
|
||||
return new Matrix4().fromArray([
|
||||
1, 0, 0, 0,
|
||||
|
@ -130,19 +129,35 @@ const setBraceCount = () => {
|
|||
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 setPillar = () => {
|
||||
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];
|
||||
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];
|
||||
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];
|
||||
bottom.position.setComponent(1, lSize * -0.5);
|
||||
|
||||
let arr = [top, pillar_outer, pillar_inner, bottom]
|
||||
arr.map(el => {
|
||||
|
@ -151,48 +166,50 @@ const setPillar = () => {
|
|||
set_material(
|
||||
{ children: [arr[2]] },
|
||||
getColorHexFromRal(pillar_color.value),
|
||||
{ pattern: pillar_pattern.value, count: lamelles_count.value },
|
||||
{ pattern: pillar_pattern.value, count: lamelles_count.value * calculateAddScale(pillar_inner) },
|
||||
true
|
||||
)
|
||||
pillar.value = arr.map(el => el.clone())
|
||||
}
|
||||
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 setFastening = () => {
|
||||
const top_one = props.models.fixing.clone().children[0];
|
||||
top_one.position.set(
|
||||
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
|
||||
)
|
||||
|
||||
const top_two = props.models.fixing.clone().children[0];
|
||||
top_two.position.set(
|
||||
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
|
||||
)
|
||||
|
||||
const v = ((extra.value as number) || fence_section.value) * 10
|
||||
|
||||
const top = props.models.fastening_top.clone().children[0];
|
||||
top.position.set(
|
||||
pillar_size * 0.5,
|
||||
lamelles_count.value * lSize - 0.0275 * scale_koef,
|
||||
lamelles_count.value * lamelle_height.value -0.0275 * scale_koef,
|
||||
0
|
||||
);
|
||||
top.scale.setComponent(0, v);
|
||||
|
||||
let c = 0.0019 * scale_koef
|
||||
const side_one = props.models.fastening_side.clone().children[0];
|
||||
side_one.name = 'side_one'
|
||||
side_one.position.set(pillar_one_pos.value, 0, 0.002 * scale_koef);
|
||||
side_one.scale.set(1, lamelles_count.value, 1)
|
||||
side_one.position.set(pillar_one_pos.value, 0, c);
|
||||
side_one.scale.set(1, lamelles_count.value * calculateAddScale(side_one), 1)
|
||||
|
||||
const side_two = props.models.fastening_side.clone().children[0];
|
||||
side_two.name = 'side_two'
|
||||
side_two.scale.set(-1, lamelles_count.value, -1)
|
||||
side_two.position.set(pillar_two_pos.value, 0, -0.005 * scale_koef);
|
||||
side_two.scale.set(-1, lamelles_count.value * calculateAddScale(side_two), -1)
|
||||
side_two.position.set(pillar_two_pos.value, 0, c);
|
||||
|
||||
let arr = [top_one, top_two, top, side_one, side_two];
|
||||
[top, side_one, side_two, ...braces.value].map(el => {
|
||||
|
@ -201,7 +218,7 @@ const setFastening = () => {
|
|||
fastening.value = arr.map(el => el.clone())
|
||||
}
|
||||
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 = () => {
|
||||
if (instanced_lamelle.value) {
|
||||
|
@ -214,6 +231,7 @@ watch([instanced_lamelle, lamelle_color], setLamellesColor)
|
|||
watch([
|
||||
instanced_lamelle,
|
||||
lamelles_count,
|
||||
lamelle_height,
|
||||
fence_section,
|
||||
], () => {
|
||||
const translationVector = new Vector3(0, 20, 20)
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Object3D, Vector3 } from 'three';
|
|||
//@ts-ignore
|
||||
import { useGLTF, } from '@tresjs/cientos'
|
||||
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 extra_section = use_extra_section()
|
||||
|
@ -20,103 +20,156 @@ const goto_target = use_goto_target()
|
|||
const { scene, controls, camera } = useTresContext()
|
||||
const { seek, seekAll } = useSeek()
|
||||
|
||||
const topper_models = {} as { [key: toppersIds]: Object3D }
|
||||
for await (const element of toppers) {
|
||||
const { scene } = await useGLTF(getModel(element.id))
|
||||
topper_models[element.id] = scene
|
||||
}
|
||||
const globalFenceType = useGlobalFenceType()
|
||||
|
||||
const { scene: model_pillar_center } = await useGLTF('/models_one/pillar/center.glb')
|
||||
const { scene: model_pillar_bottom } = await useGLTF('/models_one/pillar/bottom.glb')
|
||||
const { scene: model_pillar_inner } = await useGLTF('/models_one/pillar/inner.glb')
|
||||
const { scene: model_pillar_brace } = await useGLTF('/models_one/pillar/brace.glb')
|
||||
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 { scene: model_fastening_top } = await useGLTF('/models_one/fastening/top.glb');
|
||||
const { scene: model_fastening_side } = await useGLTF('/models_one/fastening/side.glb');
|
||||
const { scene: model_fixing } = await useGLTF('/models_one/fixing.glb');
|
||||
const total = ref(0)
|
||||
const size = ref(0)
|
||||
const count = ref(0)
|
||||
|
||||
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 top = ref(top_model)
|
||||
const pillar_top = ref(topper_models[pillar_topper.value])
|
||||
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 () => {
|
||||
pillar_top.value = topper_models[pillar_topper.value]
|
||||
})
|
||||
|
||||
const total = ref((section_count.value + ~~(!!extra_section.value)))
|
||||
const size = ref(Math.ceil(total.value / 4))
|
||||
const count = ref((total.value >= 4) ? size.value : total.value)
|
||||
|
||||
watch(() => [section_count.value, extra_section.value], () => {
|
||||
total.value = (section_count.value + ~~(!!extra_section.value))
|
||||
size.value = Math.ceil(total.value / 4);
|
||||
count.value = (total.value >= 4) ? size.value : total.value;
|
||||
|
||||
const lines_count = (total.value >= 4) ? 4 : 1
|
||||
const base = seek(scene.value, 'name', 'base')
|
||||
if (base?.children && base.children.length !== lines_count) {
|
||||
base.children = [...base?.children.slice(0, lines_count)]
|
||||
}
|
||||
|
||||
const lines = seekAll(scene.value, 'name', 'line')
|
||||
lines.forEach(line => {
|
||||
let n = size.value
|
||||
if (lines_count == 1) {
|
||||
n = total.value
|
||||
}
|
||||
if (line.name.endsWith('_4')) {
|
||||
n = total.value - size.value * 3
|
||||
if (n < 0) {
|
||||
n = 0
|
||||
}
|
||||
}
|
||||
const inner = seek(line, 'name', line.name + '_inner');
|
||||
if (inner?.children && n < inner?.children.length) {
|
||||
inner.children = [...inner?.children.slice(0, n)]
|
||||
}
|
||||
});
|
||||
})
|
||||
const setTarget = (smooth = false) => {
|
||||
let f = fence_section.value * lamelles_count.value * lamelle_height.value * 0.75;
|
||||
const max = 2.25
|
||||
if (f < max) f = max
|
||||
const target = new Vector3(0, lamelles_count.value * lamelle_height.value * 0.5, 0);
|
||||
if (smooth) {
|
||||
goto_target.value = target
|
||||
goto_cam.value = new Vector3(f, f, f)
|
||||
} else {
|
||||
(controls.value as OrbitControlsProps).target = target;
|
||||
(controls.value as any).update()
|
||||
goto_cam.value = new Vector3(f, f, f)
|
||||
}
|
||||
}
|
||||
watch([lamelles_count, fence_section], () => {
|
||||
setTarget()
|
||||
open_calc.value = []
|
||||
})
|
||||
|
||||
watch(open_calc, () => {
|
||||
if (Object.keys(open_calc.value).length == 0) {
|
||||
setTarget(true)
|
||||
}
|
||||
})
|
||||
const min_for_square = 12;
|
||||
|
||||
setTarget()
|
||||
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) {
|
||||
const { scene } = await useGLTF(getModel(element.id, key))
|
||||
topper_models[key][element.id] = scene
|
||||
}
|
||||
}
|
||||
|
||||
const { scene: model_pillar_center } = await useGLTF('/models_one/pillar/center.glb')
|
||||
const { scene: model_pillar_bottom } = await useGLTF('/models_one/pillar/bottom.glb')
|
||||
const { scene: model_pillar_inner } = await useGLTF('/models_one/pillar/inner.glb')
|
||||
const { scene: model_pillar_brace } = await useGLTF('/models_one/pillar/brace.glb')
|
||||
|
||||
const { scene: model_fastening_top } = await useGLTF('/models_one/fastening/top.glb');
|
||||
const { scene: model_fastening_side } = await useGLTF('/models_one/fastening/side.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: lamelle_model } = await useGLTF('/models_one/lamel_100.glb', { draco: true });
|
||||
|
||||
const { scene: lamelle_model_standart } = await useGLTF('/models_one/lamel_100.glb', { draco: true });
|
||||
const { scene: lamelle_model_aristo } = await useGLTF('/models_one/lamel_100_aristo.glb', { draco: true });
|
||||
|
||||
top.value = top_model
|
||||
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 setPillarTopper = () => {
|
||||
let key = 'baseToppers'
|
||||
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], () => {
|
||||
total.value = (section_count.value + ~~(!!extra_section.value))
|
||||
size.value = Math.ceil(total.value / 4);
|
||||
count.value = (total.value >= 4) ? size.value : total.value;
|
||||
|
||||
const lines_count = (total.value >= 4) ? 4 : 1
|
||||
const base = seek(scene.value, 'name', 'base')
|
||||
if (base?.children && base.children.length !== lines_count) {
|
||||
base.children = [...base?.children.slice(0, lines_count)]
|
||||
}
|
||||
|
||||
const lines = seekAll(scene.value, 'name', 'line')
|
||||
lines.forEach(line => {
|
||||
let n = size.value
|
||||
if (lines_count == 1) {
|
||||
n = total.value
|
||||
}
|
||||
if (line.name.endsWith('_4')) {
|
||||
n = total.value - size.value * 3
|
||||
if (n < 0) {
|
||||
n = 0
|
||||
}
|
||||
}
|
||||
const inner = seek(line, 'name', line.name + '_inner');
|
||||
if (inner?.children && n < inner?.children.length) {
|
||||
inner.children = [...inner?.children.slice(0, n)]
|
||||
}
|
||||
});
|
||||
})
|
||||
const setTarget = (smooth = false) => {
|
||||
let f = fence_section.value * lamelles_count.value * lamelle_height.value * 0.75;
|
||||
const max = 2.25
|
||||
if (f < max) f = max
|
||||
const target = new Vector3(0, lamelles_count.value * lamelle_height.value * 0.5, 0);
|
||||
if (smooth) {
|
||||
goto_target.value = target
|
||||
goto_cam.value = new Vector3(f, f, f)
|
||||
} else {
|
||||
(controls.value as OrbitControlsProps).target = target;
|
||||
(controls.value as any).update()
|
||||
goto_cam.value = new Vector3(f, f, f)
|
||||
}
|
||||
}
|
||||
watch([lamelles_count, fence_section, lamelle_height], () => {
|
||||
setTarget()
|
||||
open_calc.value = []
|
||||
})
|
||||
|
||||
watch(open_calc, () => {
|
||||
if (Object.keys(open_calc.value).length == 0) {
|
||||
setTarget(true)
|
||||
}
|
||||
})
|
||||
|
||||
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>
|
||||
<template>
|
||||
<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="{
|
||||
top,
|
||||
pillar_center, pillar_top, pillar_bottom, pillar_inner,
|
||||
|
|
|
@ -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>
|
|
@ -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: 'Вершина 1', filename: 'icon_stolb_verh_2.svg', model: 'decor1' },
|
||||
{ name: 'Вершина 2', filename: 'icon_stolb_verh_1.svg', model: 'decor2' },
|
||||
].map((el, i) => Object.assign(el, { id: i }))
|
||||
|
||||
export const getFilename = (id: toppersIds) => {
|
||||
const el = toppers.find(el => el.id == id)
|
||||
export const aristoToppers = [
|
||||
{ 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
|
||||
return `/topper/${el?.filename}`
|
||||
}
|
||||
|
||||
export const getName = (id: toppersIds) => {
|
||||
const el = toppers.find(el => el.id == id)
|
||||
export const getName = (id: toppersIds, type: string | null) => {
|
||||
let toppers = getToppersByType(type)
|
||||
|
||||
const el = toppers.find((el: Topper) => el.id == id)
|
||||
if (!el || !el.filename) return undefined
|
||||
return el.name
|
||||
}
|
||||
export const getModel = (id: toppersIds) => {
|
||||
const el = toppers.find(el => el.id == id)
|
||||
export const getModel = (id: toppersIds, type: string | null) => {
|
||||
let toppers = getToppersByType(type)
|
||||
|
||||
let el = toppers.find((el: Topper) => el.id == id)
|
||||
if (!el || !el.filename) return undefined
|
||||
return `/models_one/pillar/topper/${el?.model}.glb`
|
||||
}
|
||||
|
||||
export type toppersIds = typeof toppers[number]['id']
|
||||
export type toppersIds = typeof allToppers[string][number]['id']
|
|
@ -0,0 +1,7 @@
|
|||
interface calc {
|
||||
id: number
|
||||
type: 'standart' | 'aristo'
|
||||
calc: ApiCalcType
|
||||
}
|
||||
export const useGlobalFenceType = () => useState<calc | null>('fence-global-type', () => null)
|
||||
|
|
@ -32,7 +32,7 @@ if(route.path !== '/404') {
|
|||
<h1>Вы ищете страницу, которой не существует. Вернитесь на главную страницу сайта</h1>
|
||||
<p>Извините, но мы не можем найти запрашиваемую страницу. К сожалению, мы не можем помочь вам с
|
||||
покупкой забора здесь.</p>
|
||||
<p class="hidden">
|
||||
<p>
|
||||
<code>
|
||||
{{ error?.message }}
|
||||
</code>
|
||||
|
|
|
@ -10,6 +10,9 @@ export default defineNuxtConfig({
|
|||
htmlAttrs: {
|
||||
lang: 'ru',
|
||||
},
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/tabler--fence-filled.svg' },
|
||||
]
|
||||
},
|
||||
},
|
||||
ssr: true,
|
||||
|
@ -25,7 +28,9 @@ export default defineNuxtConfig({
|
|||
],
|
||||
runtimeConfig: {
|
||||
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',
|
||||
baseUrl: '',
|
||||
yandexMetrika: {
|
||||
|
|
|
@ -7,6 +7,8 @@ import { marked } from 'marked';
|
|||
|
||||
import og_img from '/og_img.png'
|
||||
|
||||
const globalFenceType = useGlobalFenceType()
|
||||
|
||||
const { data: seoData } = await apiFetch<ApiKpType>(`kp/1/`)
|
||||
useSeoMeta({
|
||||
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 { 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 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 advantagesText = computed(() => {
|
||||
let c = advantages?.content || ''
|
||||
Object.entries(calculatorData.value || {}).map(item => {
|
||||
c = c.replaceAll(`[${item[0]}]`, roubleSign.format(item[1]))
|
||||
Object.entries(calculators.value?.find(el => el.id == openTab.value) || {}).map(item => {
|
||||
c = c.replaceAll(`[${item[0]}]`, roubleSign.format(item[1] as number))
|
||||
})
|
||||
return marked.parse(c)
|
||||
})
|
||||
|
||||
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>
|
||||
<template>
|
||||
<div>
|
||||
<Modal :calcData="(calculators || []).find(el => el.id == openTab)" />
|
||||
<div class="siteblock bg-white" :id="about?.slug" v-if="about">
|
||||
<div class="container">
|
||||
<h1 class="siteblock-title">{{ about?.title }}</h1>
|
||||
|
@ -92,6 +113,24 @@ const { data: advData } = await apiFetch<ApiAdvantageType[]>(`advantage/`)
|
|||
</div>
|
||||
</div>
|
||||
<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="col-span-full xl:col-span-8">
|
||||
<ExpDiagram />
|
||||
|
@ -101,7 +140,7 @@ const { data: advData } = await apiFetch<ApiAdvantageType[]>(`advantage/`)
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="siteblock siteblock_imgbg bg-slate-500"
|
||||
<div class="siteblock siteblock_imgbg bg-slate-500 "
|
||||
:style="[{ backgroundImage: `url(${[imgBase, delivery?.image].join('/')})` }]">
|
||||
<NuxtImg :src="[imgBase, delivery?.image].join('/')" class="invisible" v-if="delivery"
|
||||
alt="коричневый забор" title="" format="webp" loading="lazy" />
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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 |
|
@ -3,6 +3,7 @@ export async function apiFetch<T>(path: string) {
|
|||
const apiBase = config.public.apiBase
|
||||
const headers = new Headers()
|
||||
headers.set('Referer', config.public.baseUrl)
|
||||
|
||||
return useFetch<T>(`${apiBase}/${path}`, {
|
||||
baseURL: config.public.baseUrl,
|
||||
headers,
|
||||
|
|
Loading…
Reference in New Issue