Убрал прошлый функционал Запчастей
This commit is contained in:
+2
-42
@@ -1,26 +1,15 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, provide } from "vue";
|
import { ref, provide } from "vue";
|
||||||
import DroneParts from "@/components/DroneParts.vue";
|
|
||||||
import MyDropMenu from "@/components/MyDropMenu.vue";
|
import MyDropMenu from "@/components/MyDropMenu.vue";
|
||||||
|
|
||||||
const showDroneParts = ref(false);
|
|
||||||
const dropMenu = ref(null);
|
const dropMenu = ref(null);
|
||||||
|
|
||||||
provide('dropMenu', dropMenu);
|
provide('dropMenu', dropMenu);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<MyDropMenu ref="dropMenu" />
|
||||||
v-if="!showDroneParts"
|
<router-view />
|
||||||
@click="showDroneParts = true"
|
|
||||||
class="show-parts-btn"
|
|
||||||
>
|
|
||||||
<img src="/images/drone.png" alt="menu" class="btn-icon" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<MyDropMenu v-if="!showDroneParts" ref="dropMenu" />
|
|
||||||
<DroneParts v-if="showDroneParts" @back="showDroneParts = false" />
|
|
||||||
<router-view v-if="!showDroneParts" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -36,33 +25,4 @@ body {
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-parts-btn {
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
z-index: 9999;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
padding: 0;
|
|
||||||
background: rgba(12, 12, 12, 0.034);
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
|
||||||
transition: transform 0.2s;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-icon {
|
|
||||||
width: 90%;
|
|
||||||
height: 90%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-parts-btn:hover {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,452 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="drone-parts-container">
|
|
||||||
<!-- Кнопка возврата в левом верхнем углу -->
|
|
||||||
<button class="back-btn" @click="$emit('back')">
|
|
||||||
← Вернуться к маршруту
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Левая панель - список компонентов -->
|
|
||||||
<div class="parts-list scroll-strip">
|
|
||||||
<ListCard
|
|
||||||
v-for="part in parts"
|
|
||||||
:key="part.id"
|
|
||||||
:title="part.name"
|
|
||||||
:subtitle="part.category"
|
|
||||||
:image="part.image"
|
|
||||||
:active="selectedPart?.id === part.id"
|
|
||||||
@click="selectPart(part)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Правая часть - детали -->
|
|
||||||
<div class="main-area">
|
|
||||||
<div v-if="selectedPart" class="part-detail">
|
|
||||||
<div class="detail-image">
|
|
||||||
<img :src="selectedPart.image" :alt="selectedPart.name" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-info">
|
|
||||||
<h2>{{ selectedPart.name }}</h2>
|
|
||||||
<p class="description">{{ selectedPart.description }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="comments-section">
|
|
||||||
<div class="comments-header">
|
|
||||||
<h3>📝 Мой комментарий</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="myComment" class="existing-comment">
|
|
||||||
<div class="comment-text">{{ myComment.text }}</div>
|
|
||||||
<div class="comment-meta">
|
|
||||||
<span class="comment-date">{{ myComment.date }}</span>
|
|
||||||
<button class="edit-comment" @click="editComment">✏️ Редактировать</button>
|
|
||||||
<button class="delete-comment" @click="deleteComment">🗑️ Удалить</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="add-comment">
|
|
||||||
<textarea
|
|
||||||
v-model="commentText"
|
|
||||||
:placeholder="`Мой комментарий к «${selectedPart.name}»...`"
|
|
||||||
rows="3"
|
|
||||||
></textarea>
|
|
||||||
<div class="comment-actions">
|
|
||||||
<button class="save-btn" @click="saveComment" :disabled="!commentText.trim()">
|
|
||||||
💾 Сохранить комментарий
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="empty-state">
|
|
||||||
<div class="empty-icon">🛸</div>
|
|
||||||
<h3>Выберите компонент дрона</h3>
|
|
||||||
<p>Нажмите на любой компонент из списка слева</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import ListCard from '@/components/UI/ListCard.vue'
|
|
||||||
|
|
||||||
const emit = defineEmits(['back'])
|
|
||||||
let db = null
|
|
||||||
|
|
||||||
const openDB = () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = indexedDB.open('DroneCommentsDB', 1)
|
|
||||||
|
|
||||||
request.onerror = () => reject(request.error)
|
|
||||||
request.onsuccess = () => resolve(request.result)
|
|
||||||
|
|
||||||
request.onupgradeneeded = (event) => {
|
|
||||||
const database = event.target.result
|
|
||||||
if (!database.objectStoreNames.contains('comments')) {
|
|
||||||
const store = database.createObjectStore('comments', { keyPath: 'id', autoIncrement: true })
|
|
||||||
store.createIndex('part_id', 'part_id', { unique: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveCommentToDB = async (partId, text) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const transaction = db.transaction(['comments'], 'readwrite')
|
|
||||||
const store = transaction.objectStore('comments')
|
|
||||||
|
|
||||||
const comment = {
|
|
||||||
part_id: partId,
|
|
||||||
user_id: 'me',
|
|
||||||
text: text,
|
|
||||||
date: new Date().toLocaleString('ru-RU'),
|
|
||||||
timestamp: Date.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = store.add(comment)
|
|
||||||
request.onsuccess = () => resolve(request.result)
|
|
||||||
request.onerror = () => reject(request.error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadCommentFromDB = async (partId) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const transaction = db.transaction(['comments'], 'readonly')
|
|
||||||
const store = transaction.objectStore('comments')
|
|
||||||
const index = store.index('part_id')
|
|
||||||
const request = index.getAll(partId)
|
|
||||||
|
|
||||||
request.onsuccess = () => {
|
|
||||||
const comments = request.result
|
|
||||||
const myComment = comments.length > 0 ? comments[comments.length - 1] : null
|
|
||||||
resolve(myComment)
|
|
||||||
}
|
|
||||||
request.onerror = () => reject(request.error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateCommentInDB = async (id, newText) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const transaction = db.transaction(['comments'], 'readwrite')
|
|
||||||
const store = transaction.objectStore('comments')
|
|
||||||
|
|
||||||
const request = store.get(id)
|
|
||||||
request.onsuccess = () => {
|
|
||||||
const comment = request.result
|
|
||||||
if (comment) {
|
|
||||||
comment.text = newText
|
|
||||||
comment.date = new Date().toLocaleString('ru-RU')
|
|
||||||
comment.timestamp = Date.now()
|
|
||||||
|
|
||||||
const updateRequest = store.put(comment)
|
|
||||||
updateRequest.onsuccess = () => resolve()
|
|
||||||
updateRequest.onerror = () => reject(updateRequest.error)
|
|
||||||
} else {
|
|
||||||
reject('Comment not found')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request.onerror = () => reject(request.error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteCommentFromDB = async (id) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const transaction = db.transaction(['comments'], 'readwrite')
|
|
||||||
const store = transaction.objectStore('comments')
|
|
||||||
const request = store.delete(id)
|
|
||||||
|
|
||||||
request.onsuccess = () => resolve()
|
|
||||||
request.onerror = () => reject(request.error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const parts = ref([])
|
|
||||||
const selectedPart = ref(null)
|
|
||||||
const myComment = ref(null)
|
|
||||||
const commentText = ref('')
|
|
||||||
const dbReady = ref(false)
|
|
||||||
const loadParts = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/droneComponents/components.json')
|
|
||||||
if (response.ok) {
|
|
||||||
parts.value = await response.json()
|
|
||||||
} else {
|
|
||||||
console.error('Файл components.json не найден')
|
|
||||||
parts.value = []
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка загрузки компонентов:', error)
|
|
||||||
parts.value = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const selectPart = async (part) => {
|
|
||||||
selectedPart.value = part
|
|
||||||
if (dbReady.value) {
|
|
||||||
const comment = await loadCommentFromDB(part.id)
|
|
||||||
myComment.value = comment
|
|
||||||
commentText.value = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== КОММЕНТАРИИ ==========
|
|
||||||
const saveComment = async () => {
|
|
||||||
if (!commentText.value.trim() || !selectedPart.value) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
await saveCommentToDB(selectedPart.value.id, commentText.value.trim())
|
|
||||||
const comment = await loadCommentFromDB(selectedPart.value.id)
|
|
||||||
myComment.value = comment
|
|
||||||
commentText.value = ''
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка сохранения:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const editComment = () => {
|
|
||||||
if (myComment.value) {
|
|
||||||
commentText.value = myComment.value.text
|
|
||||||
myComment.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteComment = async () => {
|
|
||||||
if (!confirm('Удалить комментарий?')) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteCommentFromDB(myComment.value.id)
|
|
||||||
myComment.value = null
|
|
||||||
commentText.value = ''
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка удаления:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onMounted(async () => {
|
|
||||||
try {
|
|
||||||
db = await openDB()
|
|
||||||
dbReady.value = true
|
|
||||||
await loadParts()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка открытия IndexedDB:', error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.drone-parts-container {
|
|
||||||
display: flex;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100%;
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Кнопка возврата */
|
|
||||||
.back-btn {
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
z-index: 9999;
|
|
||||||
padding: 12px 20px;
|
|
||||||
background: linear-gradient(135deg, #ff6b6b, #c92a2a);
|
|
||||||
border: none;
|
|
||||||
border-radius: 10px;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-btn:hover {
|
|
||||||
transform: scale(1.02);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Левая панель с отступом сверху, чтобы не перекрывалась кнопкой */
|
|
||||||
.parts-list {
|
|
||||||
width: 300px;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba(0, 0, 0, 0.4);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Правая область */
|
|
||||||
.main-area {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.part-detail {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
border-radius: 24px;
|
|
||||||
padding: 30px;
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-image {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-image img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 350px;
|
|
||||||
border-radius: 20px;
|
|
||||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-info h2 {
|
|
||||||
font-size: 28px;
|
|
||||||
color: #fff;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: rgba(255, 255, 255, 0.85);
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-section {
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
padding-top: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-header h3 {
|
|
||||||
font-size: 18px;
|
|
||||||
color: #fff;
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.existing-comment {
|
|
||||||
background: rgba(35, 170, 227, 0.15);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 15px;
|
|
||||||
border-left: 3px solid #23aae3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-text {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.5;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-meta {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 11px;
|
|
||||||
color: rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-meta button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 6px;
|
|
||||||
transition: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-comment {
|
|
||||||
color: #23aae3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-comment:hover {
|
|
||||||
background: rgba(35, 170, 227, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-comment {
|
|
||||||
color: #ff6b6b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-comment:hover {
|
|
||||||
background: rgba(255, 107, 107, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-comment textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
background: rgba(255, 255, 255, 0.08);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
border-radius: 12px;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: inherit;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-comment textarea:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #23aae3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-comment textarea::placeholder {
|
|
||||||
color: rgba(255, 255, 255, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-actions {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.save-btn {
|
|
||||||
background: linear-gradient(135deg, #23aae3, #1a6d8f);
|
|
||||||
border: none;
|
|
||||||
padding: 10px 24px;
|
|
||||||
border-radius: 24px;
|
|
||||||
color: white;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.save-btn:hover:not(:disabled) {
|
|
||||||
transform: scale(1.02);
|
|
||||||
box-shadow: 0 2px 10px rgba(35, 170, 227, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.save-btn:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
font-size: 80px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state h3 {
|
|
||||||
font-size: 24px;
|
|
||||||
color: #fff;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state p {
|
|
||||||
color: rgba(255, 255, 255, 0.6);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user