Files
3Dviewer/frontend/src/components/common/ConfirmDialog.vue
likegears 7af9c323f6 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>
2025-12-12 14:00:17 +08:00

190 lines
3.4 KiB
Vue

<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>