This commit is contained in:
xds
2026-03-22 12:40:33 +03:00
commit 28a5d51389
61 changed files with 6085 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
<template>
<div>
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900">Калькулятор 3D-печати</h1>
<p class="mt-1 text-sm text-gray-500">Загрузите модель, выберите материал и получите мгновенный расчёт стоимости</p>
</div>
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
<div class="lg:col-span-2 space-y-6">
<FileUploader />
<MaterialPicker @open-advisor="showAdvisor = true" />
<PrintSettings />
<div>
<button
@click="store.calculate()"
:disabled="!store.file || !store.materialId || store.loading"
class="btn-primary w-full sm:w-auto"
>
<svg v-if="store.loading" class="mr-2 h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
{{ store.loading ? 'Считаем...' : 'Рассчитать стоимость' }}
</button>
<p v-if="store.error" class="mt-2 text-sm text-red-600">{{ store.error }}</p>
</div>
</div>
<div class="lg:col-span-1">
<div class="sticky top-8">
<PriceResult />
<div v-if="!store.result" class="card text-center text-sm text-gray-400">
<svg class="mx-auto mb-3 h-12 w-12 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 15.75V18m-7.5-6.75h.008v.008H8.25v-.008zm0 2.25h.008v.008H8.25v-.008zm0 2.25h.008v.008H8.25v-.008zm0 2.25h.008v.008H8.25v-.008zm2.25-4.5h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008v-.008zm2.25-4.5h.008v.008H12.75v-.008zm0 2.25h.008v.008H12.75v-.008zm0 2.25h.008v.008H12.75v-.008zm0 2.25h.008v.008H12.75v-.008zm2.25-4.5h.008v.008H15v-.008zm0 2.25h.008v.008H15v-.008zm0 2.25h.008v.008H15v-.008z" />
</svg>
<p>Загрузите модель и выберите материал для расчёта</p>
</div>
</div>
</div>
</div>
<AiAdvisor :open="showAdvisor" @close="showAdvisor = false" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useCalculatorStore } from '../stores/calculator'
import FileUploader from '../components/FileUploader.vue'
import MaterialPicker from '../components/MaterialPicker.vue'
import PrintSettings from '../components/PrintSettings.vue'
import PriceResult from '../components/PriceResult.vue'
import AiAdvisor from '../components/AiAdvisor.vue'
const store = useCalculatorStore()
const showAdvisor = ref(false)
</script>

View File

@@ -0,0 +1,62 @@
<template>
<div>
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900">Каталог материалов</h1>
<p class="mt-1 text-sm text-gray-500">Все доступные материалы для 3D-печати</p>
</div>
<div v-if="materialsStore.loading" class="text-center py-12 text-gray-500">Загрузка...</div>
<div v-else v-for="(label, cat) in materialsStore.categories" :key="cat" class="mb-8">
<h2 class="mb-4 text-lg font-semibold text-gray-900">{{ label }}</h2>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div v-for="mat in byCategory(cat)" :key="mat.id" class="card">
<div class="flex items-center justify-between mb-3">
<h3 class="text-base font-bold text-gray-900">{{ mat.name }}</h3>
<span class="rounded-full bg-primary-100 px-2.5 py-0.5 text-xs font-semibold text-primary-700">
{{ mat.price_per_gram }} &#8381;/г
</span>
</div>
<p class="text-sm text-gray-600 mb-3">{{ mat.description }}</p>
<div class="grid grid-cols-2 gap-2 text-xs">
<div class="rounded bg-gray-50 p-2">
<span class="text-gray-500">Темп.</span>
<span class="ml-1 font-medium">{{ mat.properties.min_temp_c }}..{{ mat.properties.max_temp_c }}&deg;C</span>
</div>
<div class="rounded bg-gray-50 p-2">
<span class="text-gray-500">Прочность</span>
<span class="ml-1 font-medium">{{ mat.properties.strength }}</span>
</div>
<div class="rounded bg-gray-50 p-2">
<span class="text-gray-500">Гибкость</span>
<span class="ml-1 font-medium">{{ mat.properties.flexibility }}</span>
</div>
<div class="rounded bg-gray-50 p-2">
<span class="text-gray-500">Хим. стойк.</span>
<span class="ml-1 font-medium">{{ mat.properties.chemical_resistance }}</span>
</div>
</div>
<div v-if="mat.color_options?.length" class="mt-3 flex flex-wrap gap-1.5">
<span v-for="c in mat.color_options" :key="c" class="rounded-full bg-gray-100 px-2 py-0.5 text-[10px] text-gray-600">
{{ c }}
</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import { useMaterialsStore } from '../stores/materials'
const materialsStore = useMaterialsStore()
onMounted(() => materialsStore.fetchMaterials())
function byCategory(cat) {
return materialsStore.materials.filter((m) => m.category === cat)
}
</script>

View File

@@ -0,0 +1,45 @@
<template>
<div class="mx-auto max-w-lg">
<div class="mb-6">
<router-link to="/" class="mb-4 inline-flex items-center text-sm text-gray-500 hover:text-gray-700">
<svg class="mr-1 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
</svg>
Назад к калькулятору
</router-link>
<h1 class="text-2xl font-bold text-gray-900">Оформление заказа</h1>
</div>
<div v-if="!orderResult" class="card">
<OrderForm :calculation-id="calcId" @success="onOrderSuccess" />
</div>
<div v-else class="card text-center">
<div class="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-green-100">
<svg class="h-7 w-7 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
</div>
<h2 class="text-xl font-bold text-gray-900 mb-2">Заказ оформлен!</h2>
<p class="text-sm text-gray-600 mb-1">Номер заказа: <span class="font-semibold">{{ orderResult.order_id }}</span></p>
<p class="text-sm text-gray-600 mb-1">Сумма: <span class="font-semibold">{{ orderResult.total_rub }} &#8381;</span></p>
<p class="text-sm text-gray-600 mb-4">Готовность: <span class="font-semibold">{{ orderResult.estimated_ready_date }}</span></p>
<p class="text-xs text-gray-400 mb-4">Мы свяжемся с вами для подтверждения заказа</p>
<router-link to="/" class="btn-primary">Новый расчёт</router-link>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import OrderForm from '../components/OrderForm.vue'
const route = useRoute()
const calcId = route.params.calcId
const orderResult = ref(null)
function onOrderSuccess(data) {
orderResult.value = data
}
</script>