feat: 添加通用确认对话框组件及状态管理
This commit is contained in:
1 parent
b96df15179
commit
ffde675f3a
3 files changed
+180
No files matched your search
@@ -0,0 +1,142 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { confirmState } from '$lib/stores/confirm.svelte';
|
||||||
|
import { fade, scale } from 'svelte/transition';
|
||||||
|
import { onMount, onDestroy } from 'svelte';
|
||||||
|
|
||||||
|
// Handle escape key to cancel
|
||||||
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
|
if (confirmState.isOpen && e.key === 'Escape') {
|
||||||
|
confirmState.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
window.addEventListener('keydown', handleKeydown);
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.removeEventListener('keydown', handleKeydown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if confirmState.isOpen}
|
||||||
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div
|
||||||
|
class="confirm-backdrop"
|
||||||
|
transition:fade={{ duration: 150 }}
|
||||||
|
onclick={(e) => {
|
||||||
|
if (e.target === e.currentTarget) confirmState.cancel();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="confirm-dialog"
|
||||||
|
transition:scale={{ duration: 150, start: 0.95 }}
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div class="confirm-content">
|
||||||
|
<p>{confirmState.message}</p>
|
||||||
|
</div>
|
||||||
|
<div class="confirm-actions">
|
||||||
|
<button class="btn btn-secondary" onclick={() => confirmState.cancel()}> 取消 </button>
|
||||||
|
<button class="btn btn-danger" onclick={() => confirmState.confirm()}> 确定 </button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.confirm-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-dialog {
|
||||||
|
background: var(--bg-color, #ffffff);
|
||||||
|
color: var(--text-color, #000000);
|
||||||
|
border: 2px solid var(--text-color, #000000);
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 400px;
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-content {
|
||||||
|
padding: 24px;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-content p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px 24px;
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
border-top: 1px solid var(--border-color, rgba(0, 0, 0, 0.08));
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-color, #000000);
|
||||||
|
border: 1px solid var(--border-color, rgba(0, 0, 0, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: 1px solid #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #c82333;
|
||||||
|
border-color: #bd2130;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .confirm-backdrop {
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .confirm-dialog {
|
||||||
|
background: var(--bg-color, #1a1a1a);
|
||||||
|
border-color: var(--border-color, #333333);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .confirm-actions {
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { untrack } from 'svelte';
|
||||||
|
|
||||||
|
class ConfirmState {
|
||||||
|
isOpen = $state(false);
|
||||||
|
message = $state('');
|
||||||
|
private resolve: ((value: boolean) => void) | null = null;
|
||||||
|
|
||||||
|
async show(message: string): Promise<boolean> {
|
||||||
|
this.message = message;
|
||||||
|
this.isOpen = true;
|
||||||
|
return new Promise((res) => {
|
||||||
|
this.resolve = res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm() {
|
||||||
|
untrack(() => {
|
||||||
|
if (this.resolve) this.resolve(true);
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
untrack(() => {
|
||||||
|
if (this.resolve) this.resolve(false);
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private close() {
|
||||||
|
this.isOpen = false;
|
||||||
|
this.resolve = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const confirmState = new ConfirmState();
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Toast from '$lib/components/Toast.svelte';
|
import Toast from '$lib/components/Toast.svelte';
|
||||||
|
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
@@ -102,6 +103,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<Toast />
|
<Toast />
|
||||||
|
<ConfirmDialog />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.admin-container {
|
.admin-container {
|
||||||
|
|||||||
Reference in new issue
Block a user