Initial commit: 3D Viewer application
Features: - Vue 3 frontend with Three.js/Online3DViewer - Node.js API with PostgreSQL and Redis - Python worker for model conversion - Docker Compose for deployment - ViewCube navigation with drag rotation and 90° snap - Cross-section, exploded view, and render settings - Parts tree with visibility controls 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
189
frontend/src/components/common/ConfirmDialog.vue
Normal file
189
frontend/src/components/common/ConfirmDialog.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, watch } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
title: string
|
||||
message: string
|
||||
confirmText?: string
|
||||
cancelText?: string
|
||||
danger?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
confirm: []
|
||||
cancel: []
|
||||
}>()
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape' && props.show) {
|
||||
emit('cancel')
|
||||
}
|
||||
}
|
||||
|
||||
function handleOverlayClick(e: MouseEvent) {
|
||||
if ((e.target as HTMLElement).classList.contains('dialog-overlay')) {
|
||||
emit('cancel')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleKeydown)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', handleKeydown)
|
||||
})
|
||||
|
||||
// Prevent body scroll when dialog is open
|
||||
watch(() => props.show, (show) => {
|
||||
if (show) {
|
||||
document.body.style.overflow = 'hidden'
|
||||
} else {
|
||||
document.body.style.overflow = ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="show"
|
||||
class="dialog-overlay"
|
||||
@click="handleOverlayClick"
|
||||
>
|
||||
<div class="dialog-modal">
|
||||
<div class="dialog-header">
|
||||
<h3>{{ title }}</h3>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button class="btn-cancel" @click="emit('cancel')">
|
||||
{{ cancelText || '取消' }}
|
||||
</button>
|
||||
<button
|
||||
class="btn-confirm"
|
||||
:class="{ danger }"
|
||||
@click="emit('confirm')"
|
||||
>
|
||||
{{ confirmText || '确认' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
backdrop-filter: blur(4px);
|
||||
animation: fade-in 0.15s ease;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.dialog-modal {
|
||||
background: var(--bg-primary);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-xl);
|
||||
min-width: 320px;
|
||||
max-width: 400px;
|
||||
animation: scale-in 0.15s ease;
|
||||
}
|
||||
|
||||
@keyframes scale-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
padding: var(--space-4) var(--space-5);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.dialog-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.dialog-body p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
justify-content: flex-end;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-confirm {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background: var(--bg-sunken);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-confirm:hover {
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
|
||||
.btn-confirm.danger {
|
||||
background: var(--danger-color);
|
||||
}
|
||||
|
||||
.btn-confirm.danger:hover {
|
||||
background: #dc2626;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user