letoceiling-coder/ceiling-calculator
Composer 安装命令:
composer require letoceiling-coder/ceiling-calculator
包简介
Калькулятор натяжных потолков для Laravel с поддержкой Vue 3
README 文档
README
📦 Профессиональный калькулятор натяжных потолков для Laravel
Независимый Laravel-пакет для расчета стоимости натяжных потолков с интерактивным чертежом, выбором материалов, цветов и автоматическим формированием сметы.
✅ Подтверждение
Да! Пакет устанавливается через Composer и Vue 3 компонент полностью работает с API!
Что работает из коробки:
- ✅ Установка через Composer -
composer require letoceiling-coder/ceiling-calculator:dev-main - ✅ Vue 3 интеграция - Полная поддержка Vue 3 компонентов
- ✅ API endpoints - Автоматически регистрируются после установки
- ✅ Подключение к API - Vue компонент автоматически загружает данные через API
- ✅ Сохранение расчетов - Vue компонент сохраняет расчеты через API
- ✅ Laravel Passport - Поддержка авторизации через Passport (опционально)
Доступные API Endpoints:
После установки автоматически доступны:
GET /api/ceiling/rooms- Список помещенийGET /api/ceiling/materials- Список материалов (текстуры, производители, цвета)POST /api/ceiling- Сохранить расчетPUT /api/ceiling/{id}- Обновить расчетDELETE /api/ceiling/{id}- Удалить расчет
Vue 3 компонент:
<CeilingCalculator api-url="/api/ceiling" :use-auth="true" @calculation-saved="onCalculationSaved" />
См. QUICK_START_VUE3.md для быстрого старта и VUE3_API_INTEGRATION.md для полного руководства
Все работает из коробки! Нет необходимости в дополнительной настройке! 🎉
📖 О проекте
Ceiling Calculator - это полнофункциональный калькулятор для расчета стоимости натяжных потолков, который позволяет:
- 🎨 Создавать интерактивные чертежи помещений с помощью canvas
- 📐 Рассчитывать площадь и периметр потолка автоматически
- 🎨 Выбирать текстуры и производителей из базы данных
- 🌈 Выбирать цвета потолка
- 💰 Автоматически формировать смету с детализацией работ и материалов
- 💾 Сохранять расчеты для последующего использования
- 📄 Экспортировать сметы в удобном формате
Пакет полностью независим и содержит все необходимые ресурсы (CSS, JS, изображения, иконки), не требует дополнительных зависимостей от основного проекта.
✨ Основной функционал
🎯 Основные возможности
1. Интерактивное создание чертежей
- Рисование контуров помещений на canvas
- Автоматический расчет площади и периметра
- Поддержка сложных форм (многоугольники, кривые)
- Добавление отверстий и проемов
- Измерение углов и расстояний
2. Выбор материалов
- База данных текстур/фактур (матовые, глянцевые, сатиновые и т.д.)
- Список производителей (Pongs, Clipso, Alkor Draka и др.)
- Выбор цвета из палитры
- Выбор ширины полотна
3. Расчет стоимости
- Автоматический расчет площади потолка
- Расчет длины швов
- Учет усадки материала
- Расчет стоимости материалов
- Расчет стоимости работ (монтаж, установка светильников и т.д.)
- Расчет коммуникаций (обход труб, проводов)
- Формирование итоговой сметы
4. Управление расчетами
- Сохранение расчетов в базу данных
- Просмотр истории расчетов
- Редактирование существующих расчетов
- Удаление расчетов
- Экспорт смет
5. Работа с помещениями
- Создание списка помещений
- Привязка расчетов к помещениям
- Группировка расчетов по помещениям
🎨 Интерфейс
- Адаптивный дизайн - работает на всех устройствах
- Интуитивное управление - простое и понятное
- Визуальный редактор - чертежи с помощью мыши/касаний
- Живой предпросмотр - изменения видны сразу
📦 Установка через Composer
Требования
- PHP >= 8.1
- Laravel >= 10.0
- Composer >= 2.0
- MySQL/PostgreSQL/SQLite
Шаг 1: Добавить репозиторий
В composer.json вашего Laravel проекта добавьте репозиторий:
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/letoceiling-coder/cieling-vue.git"
}
],
"require": {
"letoceiling-coder/ceiling-calculator": "dev-main"
}
}
Для установки из Packagist (после публикации):
{
"require": {
"letoceiling-coder/ceiling-calculator": "^1.0"
}
}
Шаг 2: Установить пакет
composer require letoceiling-coder/ceiling-calculator:dev-main
Шаг 3: Публикация ресурсов
php artisan vendor:publish --tag=ceiling-config php artisan vendor:publish --tag=ceiling-assets php artisan vendor:publish --tag=ceiling-migrations
Или используйте команду установки (выполняет все вышеперечисленное):
php artisan ceiling:install
Шаг 4: Запустить миграции
php artisan migrate
Шаг 5: Заполнить базу данных (опционально)
php artisan db:seed --class=Database\\Seeders\\CeilingDatabaseSeeder
Это заполнит базу данных начальными данными:
- 20 текстур/фактур
- 11 производителей
- Примеры цветов
Шаг 6: Проверить установку
Откройте в браузере:
http://your-domain.com/ceilng
🎨 Интеграция Vue 3
Пакет поддерживает опциональную интеграцию Vue 3 для создания интерактивных компонентов.
Шаг 1: Включить Vue 3
В файле .env:
CEILING_VUE_ENABLED=true CEILING_VUE_MODE=production
Или в config/ceiling.php:
'vue' => [ 'enabled' => true, 'mode' => 'production', // или 'development' 'cdn' => 'https://unpkg.com/vue@3/dist/vue.global', ],
Шаг 2: Регистрация Vue компонента
Создайте файл resources/js/ceiling-calculator.js:
import { createApp } from 'vue'; import CeilingCalculator from './components/CeilingCalculator.vue'; // Регистрация компонента глобально window.createCeilingApp = function(containerId, props = {}) { const app = createApp(CeilingCalculator, props); app.mount(containerId); return app; }; // Или регистрация через plugin export default { install(app) { app.component('CeilingCalculator', CeilingCalculator); } };
Шаг 3: Создание компонента Vue
Создайте файл resources/js/components/CeilingCalculator.vue:
<template> <div class="ceiling-calculator"> <div class="calculator-header"> <h2>{{ title }}</h2> <button @click="toggleMode" class="btn btn-primary"> {{ mode === 'draw' ? 'Режим просмотра' : 'Режим рисования' }} </button> </div> <div class="calculator-body"> <!-- Выбор помещения --> <div class="room-selector" v-if="showRoomSelector"> <label>Выберите помещение:</label> <select v-model="selectedRoom" @change="onRoomChange"> <option v-for="room in rooms" :key="room.id" :value="room.id"> {{ room.name }} </option> </select> </div> <!-- Canvas для чертежа --> <div class="canvas-container"> <canvas ref="canvas" @mousedown="onMouseDown" @mousemove="onMouseMove" @mouseup="onMouseUp" ></canvas> </div> <!-- Панель параметров --> <div class="parameters-panel"> <div class="parameter-group"> <label>Площадь:</label> <span class="value">{{ area }} м²</span> </div> <div class="parameter-group"> <label>Периметр:</label> <span class="value">{{ perimeter }} м</span> </div> <div class="parameter-group"> <label>Выберите текстуру:</label> <select v-model="selectedTexture" @change="onTextureChange"> <option v-for="texture in textures" :key="texture.id" :value="texture.id"> {{ texture.title }} </option> </select> </div> <div class="parameter-group"> <label>Выберите производителя:</label> <select v-model="selectedManufacturer" @change="onManufacturerChange"> <option v-for="manufacturer in manufacturers" :key="manufacturer.id" :value="manufacturer.id"> {{ manufacturer.name }} </option> </select> </div> <div class="parameter-group"> <label>Выберите цвет:</label> <div class="color-picker"> <div v-for="color in colors" :key="color.id" class="color-item" :class="{ active: selectedColor === color.id }" :style="{ backgroundColor: color.hex_color }" @click="selectColor(color.id)" ></div> </div> </div> </div> <!-- Расчет стоимости --> <div class="cost-calculation"> <h3>Расчет стоимости</h3> <div class="cost-item"> <span>Материалы:</span> <span class="cost">{{ materialCost }} ₽</span> </div> <div class="cost-item"> <span>Работы:</span> <span class="cost">{{ workCost }} ₽</span> </div> <div class="cost-item total"> <span>Итого:</span> <span class="cost">{{ totalCost }} ₽</span> </div> <button @click="saveCalculation" class="btn btn-success"> Сохранить расчет </button> </div> </div> </div> </template> <script> export default { name: 'CeilingCalculator', props: { // Основные props title: { type: String, default: 'Калькулятор натяжных потолков' }, initialData: { type: Object, default: () => ({}) }, apiUrl: { type: String, default: '/api/ceiling' }, // Конфигурация showRoomSelector: { type: Boolean, default: true }, editable: { type: Boolean, default: true }, // Данные rooms: { type: Array, default: () => [] }, textures: { type: Array, default: () => [] }, manufacturers: { type: Array, default: () => [] }, colors: { type: Array, default: () => [] }, // Состояние selectedRoom: { type: [Number, String], default: null }, selectedTexture: { type: [Number, String], default: null }, selectedManufacturer: { type: [Number, String], default: null }, selectedColor: { type: [Number, String], default: null } }, data() { return { mode: 'draw', // 'draw' или 'view' area: 0, perimeter: 0, materialCost: 0, workCost: 0, canvas: null, ctx: null, drawing: false, currentPath: [], paths: [] }; }, computed: { totalCost() { return this.materialCost + this.workCost; } }, mounted() { this.initCanvas(); this.loadInitialData(); }, methods: { initCanvas() { this.canvas = this.$refs.canvas; this.ctx = this.canvas.getContext('2d'); // Установка размеров canvas const container = this.canvas.parentElement; this.canvas.width = container.clientWidth; this.canvas.height = container.clientHeight; // Инициализация canvas this.ctx.strokeStyle = '#000'; this.ctx.lineWidth = 2; }, loadInitialData() { if (this.initialData) { if (this.initialData.rooms) { this.rooms = this.initialData.rooms; } if (this.initialData.textures) { this.textures = this.initialData.textures; } if (this.initialData.manufacturers) { this.manufacturers = this.initialData.manufacturers; } if (this.initialData.colors) { this.colors = this.initialData.colors; } } }, onMouseDown(e) { if (!this.editable || this.mode !== 'draw') return; this.drawing = true; const rect = this.canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; this.currentPath = [{ x, y }]; this.ctx.beginPath(); this.ctx.moveTo(x, y); }, onMouseMove(e) { if (!this.drawing) return; const rect = this.canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; this.currentPath.push({ x, y }); this.ctx.lineTo(x, y); this.ctx.stroke(); }, onMouseUp() { if (!this.drawing) return; this.drawing = false; if (this.currentPath.length > 2) { this.paths.push([...this.currentPath]); this.calculateArea(); } }, calculateArea() { // Расчет площади и периметра на основе путей // Это упрощенный пример, реальная реализация будет более сложной let totalArea = 0; let totalPerimeter = 0; this.paths.forEach(path => { // Расчет площади по формуле Гаусса (Shoelace formula) let area = 0; for (let i = 0; i < path.length; i++) { const j = (i + 1) % path.length; area += path[i].x * path[j].y; area -= path[j].x * path[i].y; } totalArea += Math.abs(area) / 2; // Расчет периметра for (let i = 0; i < path.length; i++) { const j = (i + 1) % path.length; const dx = path[j].x - path[i].x; const dy = path[j].y - path[i].y; totalPerimeter += Math.sqrt(dx * dx + dy * dy); } }); // Конвертация пикселей в метры (пример: 1px = 0.01м) const scale = 0.01; this.area = (totalArea * scale * scale).toFixed(2); this.perimeter = (totalPerimeter * scale).toFixed(2); this.recalculateCosts(); }, recalculateCosts() { // Расчет стоимости на основе выбранных параметров // Это упрощенный пример const pricePerSquareMeter = 500; // Цена за м² this.materialCost = (this.area * pricePerSquareMeter).toFixed(2); this.workCost = (this.area * 300).toFixed(2); // Работы за м² }, toggleMode() { this.mode = this.mode === 'draw' ? 'view' : 'draw'; }, onRoomChange() { this.$emit('room-changed', this.selectedRoom); }, onTextureChange() { this.$emit('texture-changed', this.selectedTexture); this.recalculateCosts(); }, onManufacturerChange() { this.$emit('manufacturer-changed', this.selectedManufacturer); this.recalculateCosts(); }, selectColor(colorId) { this.selectedColor = colorId; this.$emit('color-changed', colorId); this.recalculateCosts(); }, async saveCalculation() { try { const response = await fetch(this.apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.getAuthToken()}` }, body: JSON.stringify({ room_id: this.selectedRoom, texture_id: this.selectedTexture, manufacturer_id: this.selectedManufacturer, color_id: this.selectedColor, area: this.area, perimeter: this.perimeter, material_cost: this.materialCost, work_cost: this.workCost, total_cost: this.totalCost, drawing_data: this.paths }) }); if (response.ok) { const result = await response.json(); this.$emit('calculation-saved', result); alert('Расчет успешно сохранен!'); } else { throw new Error('Ошибка сохранения'); } } catch (error) { console.error('Ошибка сохранения расчета:', error); alert('Ошибка сохранения расчета'); } }, getAuthToken() { // Получение токена для Laravel Passport return localStorage.getItem('access_token') || ''; } } }; </script> <style scoped> .ceiling-calculator { width: 100%; max-width: 1200px; margin: 0 auto; } .calculator-header { display: flex; justify-content: space-between; align-items: center; padding: 20px; background: #f5f5f5; border-radius: 8px; margin-bottom: 20px; } .calculator-body { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; } .canvas-container { border: 2px solid #ddd; border-radius: 8px; overflow: hidden; } canvas { width: 100%; height: 500px; cursor: crosshair; } .parameters-panel { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .parameter-group { margin-bottom: 20px; } .parameter-group label { display: block; margin-bottom: 5px; font-weight: bold; } .color-picker { display: flex; flex-wrap: wrap; gap: 10px; } .color-item { width: 40px; height: 40px; border-radius: 4px; cursor: pointer; border: 2px solid transparent; } .color-item.active { border-color: #333; } .cost-calculation { background: #f9f9f9; padding: 20px; border-radius: 8px; margin-top: 20px; } .cost-item { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #ddd; } .cost-item.total { font-weight: bold; font-size: 1.2em; border-bottom: none; } .btn { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } .btn-primary { background: #007bff; color: white; } .btn-success { background: #28a745; color: white; width: 100%; margin-top: 10px; } </style>
Шаг 4: Использование компонента
Вариант 1: Глобальная регистрация
В вашем Blade шаблоне:
<div id="ceiling-calculator"></div> @push('scripts') <script> document.addEventListener('DOMContentLoaded', function() { const props = { title: 'Калькулятор натяжных потолков', rooms: @json($data['rooms'] ?? []), textures: @json($data['textures'] ?? []), manufacturers: @json($data['manufacturers'] ?? []), colors: @json($data['colors'] ?? []), apiUrl: '{{ config('ceiling.api.prefix') }}', showRoomSelector: true, editable: true }; window.createCeilingApp('#ceiling-calculator', props); }); </script> @endpush
Вариант 2: Локальное использование
<template> <div> <CeilingCalculator :rooms="rooms" :textures="textures" :manufacturers="manufacturers" :colors="colors" :api-url="apiUrl" @calculation-saved="onCalculationSaved" /> </div> </template> <script> import CeilingCalculator from './components/CeilingCalculator.vue'; export default { components: { CeilingCalculator }, data() { return { rooms: [], textures: [], manufacturers: [], colors: [], apiUrl: '/api/ceiling' }; }, methods: { onCalculationSaved(result) { console.log('Расчет сохранен:', result); } } }; </script>
Props компонента
| Prop | Тип | По умолчанию | Описание |
|---|---|---|---|
title |
String | 'Калькулятор натяжных потолков' |
Заголовок калькулятора |
initialData |
Object | {} |
Начальные данные |
apiUrl |
String | '/api/ceiling' |
URL API для сохранения |
showRoomSelector |
Boolean | true |
Показывать выбор помещения |
editable |
Boolean | true |
Разрешить редактирование |
rooms |
Array | [] |
Список помещений |
textures |
Array | [] |
Список текстур |
manufacturers |
Array | [] |
Список производителей |
colors |
Array | [] |
Список цветов |
selectedRoom |
Number/String | null |
Выбранное помещение |
selectedTexture |
Number/String | null |
Выбранная текстура |
selectedManufacturer |
Number/String | null |
Выбранный производитель |
selectedColor |
Number/String | null |
Выбранный цвет |
События (Events)
| Событие | Параметры | Описание |
|---|---|---|
room-changed |
roomId |
Выбрано помещение |
texture-changed |
textureId |
Выбрана текстура |
manufacturer-changed |
manufacturerId |
Выбран производитель |
color-changed |
colorId |
Выбран цвет |
calculation-saved |
result |
Расчет сохранен |
🔐 Интеграция с Laravel Passport
Пакет поддерживает аутентификацию через Laravel Passport для защиты API.
Шаг 1: Установка Laravel Passport
composer require laravel/passport php artisan migrate php artisan passport:install
Шаг 2: Настройка модели User
В app/Models/User.php:
use Laravel\Passport\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; // ... }
Шаг 3: Настройка AuthServiceProvider
В app/Providers/AuthServiceProvider.php:
use Laravel\Passport\Passport; public function boot() { $this->registerPolicies(); Passport::routes(); // Настройка токенов Passport::tokensExpireIn(now()->addDays(15)); Passport::refreshTokensExpireIn(now()->addDays(30)); }
Шаг 4: Защита API роутов
В config/ceiling.php:
'api' => [ 'prefix' => env('CEILING_API_PREFIX', 'api/ceiling'), 'middleware' => ['api', 'auth:api'], // Добавлен auth:api 'rate_limit' => env('CEILING_API_RATE_LIMIT', 60), ],
Шаг 5: Использование токенов в компоненте Vue
Компонент автоматически использует токен из localStorage:
getAuthToken() { return localStorage.getItem('access_token') || ''; }
Для сохранения токена после авторизации:
// После успешной авторизации localStorage.setItem('access_token', response.data.access_token);
Шаг 6: Пример авторизации
// Получение токена async function login(email, password) { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email, password }) }); const data = await response.json(); localStorage.setItem('access_token', data.access_token); return data; }
Использование токенов в запросах
Компонент автоматически добавляет токен в заголовки:
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.getAuthToken()}` }
📚 Использование
Через контроллер
use LetoCeilingCoder\CeilingCalculator\Http\Controllers\CeilingController; Route::get('/ceilng', [CeilingController::class, 'index']);
Через сервис
use LetoCeilingCoder\CeilingCalculator\CeilingService; $service = app(CeilingService::class); $data = $service->getInitialData($request);
Через фасад
use LetoCeilingCoder\CeilingCalculator\Facades\Ceiling; $data = Ceiling::getInitialData($request); $rooms = Ceiling::getRooms();
📖 Дополнительная документация
QUICK_START_VUE3.md- Быстрый старт с Vue 3 и API ⭐VUE3_API_INTEGRATION.md- Полное руководство по Vue 3 с API ⭐COMPOSER_INSTALLATION.md- подробная установка через ComposerCOMPOSER_QUICK_START.md- быстрый старт с ComposerVUE3_INTEGRATION.md- интеграция Vue 3 (базовое руководство)LARAVEL_PASSPORT.md- настройка Laravel PassportASSETS_CHECKLIST.md- чеклист ресурсовPACKAGE_INDEPENDENCE.md- независимость пакетаdatabase/README.md- структура базы данных
🔧 Требования
- PHP >= 8.1
- Laravel >= 10.0
- Composer >= 2.0
- MySQL/PostgreSQL/SQLite
- Laravel Passport (опционально, для API аутентификации)
📝 Лицензия
MIT
👥 Авторы
LetoCeiling Coder
🔗 Репозиторий
GitHub: https://github.com/letoceiling-coder/cieling-vue
🆘 Поддержка
Для вопросов и поддержки создайте Issue в репозитории проекта.
letoceiling-coder/ceiling-calculator 适用场景与选型建议
letoceiling-coder/ceiling-calculator 是一款 基于 JavaScript 开发的 Composer 扩展包,目前已累计 1 次下载、GitHub Stars 达 0, 最近一次更新时间为 2025 年 11 月 15 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「laravel」 「calculator」 「ceiling」 「vue3」 「натяжные потолки」 「кал�」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 letoceiling-coder/ceiling-calculator 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 letoceiling-coder/ceiling-calculator 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 letoceiling-coder/ceiling-calculator 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Rounds a number to the nearest multiple of another number (mround, ceiling, floor)
Yii2 extension help to get the result of the calculating from the aggregated delivery services
Alfabank REST API integration
SDK com regras para cálculo de juros.
Biblioteca para calcular juros em parcelamentos.
Configurator skeleton for those using weapons for DrD+
统计信息
- 总下载量: 1
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 34
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-11-15