Убрал прошлый функционал Запчастей
This commit is contained in:
@@ -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