187 lines
3.8 KiB
Vue
187 lines
3.8 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
visible: boolean;
|
|
title?: string;
|
|
message: string;
|
|
confirmText?: string;
|
|
cancelText?: string;
|
|
danger?: boolean;
|
|
loading?: boolean;
|
|
}>(),
|
|
{
|
|
danger: false,
|
|
loading: false,
|
|
},
|
|
);
|
|
|
|
const emit = defineEmits<{
|
|
'update:visible': [value: boolean];
|
|
confirm: [];
|
|
cancel: [];
|
|
}>();
|
|
|
|
const { t } = useI18n();
|
|
|
|
const resolvedTitle = computed(() => props.title ?? t('common.confirm'));
|
|
const resolvedConfirmText = computed(() => props.confirmText ?? t('common.confirm'));
|
|
const resolvedCancelText = computed(() => props.cancelText ?? t('common.cancel'));
|
|
|
|
function close() {
|
|
if (props.loading) return;
|
|
emit('update:visible', false);
|
|
emit('cancel');
|
|
}
|
|
|
|
function onConfirm() {
|
|
if (props.loading) return;
|
|
emit('confirm');
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Teleport to="body">
|
|
<Transition name="confirm-fade">
|
|
<div
|
|
v-if="visible"
|
|
class="confirm-overlay"
|
|
@click.self="close"
|
|
>
|
|
<div
|
|
class="confirm-modal"
|
|
role="alertdialog"
|
|
aria-modal="true"
|
|
:aria-labelledby="title ? 'confirm-dialog-title' : undefined"
|
|
:aria-describedby="'confirm-dialog-message'"
|
|
>
|
|
<h2 v-if="title" id="confirm-dialog-title" class="confirm-title">{{ resolvedTitle }}</h2>
|
|
<p id="confirm-dialog-message" class="confirm-message">{{ message }}</p>
|
|
|
|
<div class="confirm-actions">
|
|
<button
|
|
type="button"
|
|
class="confirm-btn cancel"
|
|
:disabled="loading"
|
|
@click="close"
|
|
>
|
|
{{ resolvedCancelText }}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="confirm-btn confirm"
|
|
:class="{ danger }"
|
|
:disabled="loading"
|
|
@click="onConfirm"
|
|
>
|
|
{{ resolvedConfirmText }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.confirm-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 1000;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
|
|
background: rgba(0, 0, 0, 0.48);
|
|
backdrop-filter: blur(4px);
|
|
-webkit-backdrop-filter: blur(4px);
|
|
}
|
|
|
|
.confirm-modal {
|
|
width: 100%;
|
|
max-width: 340px;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
padding: 22px 18px 16px;
|
|
box-shadow: 0 8px 32px rgba(0, 61, 107, 0.12);
|
|
}
|
|
|
|
.confirm-title {
|
|
margin: 0 0 10px;
|
|
font-size: 17px;
|
|
font-weight: 800;
|
|
color: var(--primary);
|
|
text-align: center;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.confirm-message {
|
|
margin: 0 0 20px;
|
|
font-size: 14px;
|
|
line-height: 1.6;
|
|
color: var(--text-muted);
|
|
text-align: center;
|
|
}
|
|
|
|
.confirm-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.confirm-btn {
|
|
flex: 1;
|
|
min-height: 44px;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: opacity 0.15s;
|
|
}
|
|
|
|
.confirm-btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.confirm-btn.cancel {
|
|
border: 1px solid var(--border);
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.confirm-btn.confirm {
|
|
border: none;
|
|
background: var(--primary);
|
|
color: #fff;
|
|
}
|
|
|
|
.confirm-btn.confirm.danger {
|
|
background: #dc2626;
|
|
color: #fff;
|
|
}
|
|
|
|
.confirm-fade-enter-active,
|
|
.confirm-fade-leave-active {
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.confirm-fade-enter-active .confirm-modal,
|
|
.confirm-fade-leave-active .confirm-modal {
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.confirm-fade-enter-from,
|
|
.confirm-fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
.confirm-fade-enter-from .confirm-modal,
|
|
.confirm-fade-leave-to .confirm-modal {
|
|
transform: scale(0.96);
|
|
}
|
|
</style>
|